diff --git a/composer.json b/composer.json index 8bf5643c3a2..3d88fdd644e 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", + "jetbrains/phpstorm-stubs": "dev-master#b51c647ece0a88c59fbc94028daebd047377bcdb", "phpstan/phpstan": "1.10.14", "phpstan/phpstan-phpunit": "1.3.11", "phpstan/phpstan-strict-rules": "^1.5", @@ -49,7 +49,7 @@ "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4.30|^5.4|^6.0", - "vimeo/psalm": "4.30.0" + "vimeo/psalm": "5.12.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c3f3a86737e..f440d54738f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -19,6 +19,7 @@ parameters: - src/Driver/AbstractOracleDriver/EasyConnectString.php - src/Platforms/*Platform.php - src/Schema/*SchemaManager.php + - tests/TestUtil.php # In some namespaces, we use array, some elements of which are actually boolean - @@ -89,6 +90,13 @@ parameters: count: 1 path: src/Driver/Mysqli/Connection.php + # https://github.com/phpstan/phpstan/issues/9429 + - + message: '~^Strict comparison using === between int<0, max> and false will always evaluate to false.$~' + paths: + - src/Driver/IBMDB2/Connection.php + - src/Driver/IBMDB2/Result.php + includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon diff --git a/psalm-strict.xml b/psalm-strict.xml index 5ca9b450f6a..144906e2594 100644 --- a/psalm-strict.xml +++ b/psalm-strict.xml @@ -13,4 +13,14 @@ + + + + + + + + diff --git a/psalm.xml.dist b/psalm.xml.dist index 2f265e64a39..d472e5480aa 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -2,6 +2,8 @@ + + + + + + + + + + @@ -104,6 +127,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -212,6 +272,19 @@ + + + + + + + + + + + + + diff --git a/src/Connection.php b/src/Connection.php index bafb95fd84f..bd788c86af4 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -33,6 +33,7 @@ use Traversable; use function array_key_exists; +use function array_merge; use function assert; use function count; use function implode; @@ -47,7 +48,7 @@ * * @psalm-import-type Params from DriverManager * @psalm-type WrapperParameterType = string|Type|ParameterType|ArrayParameterType - * @psalm-type WrapperParameterTypeArray = array|array + * @psalm-type WrapperParameterTypeArray = array, WrapperParameterType>|array * @psalm-consistent-constructor */ class Connection implements ServerVersionProvider @@ -347,19 +348,14 @@ public function isTransactionActive(): bool /** * Adds condition based on the criteria to the query components * - * @param array $criteria Map of key columns to their values - * @param array $columns Column names - * @param array $values Column values - * @param array $conditions Key conditions + * @param array $criteria Map of key columns to their values * - * @throws Exception + * @return array{list, list, list} */ - private function addCriteriaCondition( - array $criteria, - array &$columns, - array &$values, - array &$conditions, - ): void { + private function getCriteriaCondition(array $criteria): array + { + $columns = $values = $conditions = []; + foreach ($criteria as $columnName => $value) { if ($value === null) { $conditions[] = $columnName . ' IS NULL'; @@ -370,6 +366,8 @@ private function addCriteriaCondition( $values[] = $value; $conditions[] = $columnName . ' = ?'; } + + return [$columns, $values, $conditions]; } /** @@ -377,8 +375,8 @@ private function addCriteriaCondition( * * Table expression and columns are not escaped and are not safe for user-input. * - * @param array $criteria - * @param array|array $types + * @param array $criteria + * @param array, string|ParameterType|Type>|array $types * * @return int|numeric-string The number of affected rows. * @@ -386,9 +384,7 @@ private function addCriteriaCondition( */ public function delete(string $table, array $criteria = [], array $types = []): int|string { - $columns = $values = $conditions = []; - - $this->addCriteriaCondition($criteria, $columns, $values, $conditions); + [$columns, $values, $conditions] = $this->getCriteriaCondition($criteria); $sql = 'DELETE FROM ' . $table; @@ -443,9 +439,9 @@ public function getTransactionIsolation(): TransactionIsolationLevel * * Table expression and columns are not escaped and are not safe for user-input. * - * @param array $data - * @param array $criteria - * @param array|array $types + * @param array $data + * @param array $criteria + * @param array, string|ParameterType|Type>|array $types * * @return int|numeric-string The number of affected rows. * @@ -461,7 +457,11 @@ public function update(string $table, array $data, array $criteria = [], array $ $set[] = $columnName . ' = ?'; } - $this->addCriteriaCondition($criteria, $columns, $values, $conditions); + [$criteriaColumns, $criteriaValues, $criteriaConditions] = $this->getCriteriaCondition($criteria); + + $columns = array_merge($columns, $criteriaColumns); + $values = array_merge($values, $criteriaValues); + $conditions = array_merge($conditions, $criteriaConditions); if (is_string(key($types))) { $types = $this->extractTypeValues($columns, $types); @@ -481,8 +481,8 @@ public function update(string $table, array $data, array $criteria = [], array $ * * Table expression and columns are not escaped and are not safe for user-input. * - * @param array $data - * @param array|array $types + * @param array $data + * @param array, string|ParameterType|Type>|array $types * * @return int|numeric-string The number of affected rows. * @@ -518,7 +518,7 @@ public function insert(string $table, array $data, array $types = []): int|strin * @param array $columns * @param array|array $types * - * @return array|array + * @return array, string|ParameterType|Type> */ private function extractTypeValues(array $columns, array $types): array { @@ -1308,13 +1308,13 @@ final public function convertException(Driver\Exception $e): DriverException } /** - * @param array|array $params + * @param list|array $params * @psalm-param WrapperParameterTypeArray $types * * @return array{ * string, - * array|array, - * array|array + * list|array, + * array, string|ParameterType|Type>|array * } */ private function expandArrayParameters(string $sql, array $params, array $types): array diff --git a/src/Connections/PrimaryReadReplicaConnection.php b/src/Connections/PrimaryReadReplicaConnection.php index 547b7c22777..9a94d839a9a 100644 --- a/src/Connections/PrimaryReadReplicaConnection.php +++ b/src/Connections/PrimaryReadReplicaConnection.php @@ -218,9 +218,14 @@ public function ensureConnectedToReplica(): void protected function connectTo(string $connectionName): DriverConnection { $params = $this->getParams(); - assert(isset($params['primary'], $params['replica'])); + assert(isset($params['primary'])); - $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); + if ($connectionName === 'primary') { + $connectionParams = $params['primary']; + } else { + assert(isset($params['replica'])); + $connectionParams = $this->chooseReplicaConnectionParameters($params['primary'], $params['replica']); + } try { return $this->driver->connect($connectionParams); @@ -230,28 +235,25 @@ protected function connectTo(string $connectionName): DriverConnection } /** - * @param array $params - * @psalm-param array{primary: OverrideParams, replica: array} $params + * @param OverrideParams $primary + * @param array $replicas * * @return array * @psalm-return OverrideParams */ - protected function chooseConnectionConfiguration( - string $connectionName, + protected function chooseReplicaConnectionParameters( #[SensitiveParameter] - array $params, + array $primary, + #[SensitiveParameter] + array $replicas, ): array { - if ($connectionName === 'primary') { - return $params['primary']; - } - - $config = $params['replica'][array_rand($params['replica'])]; + $params = $replicas[array_rand($replicas)]; - if (! isset($config['charset']) && isset($params['primary']['charset'])) { - $config['charset'] = $params['primary']['charset']; + if (! isset($params['charset']) && isset($primary['charset'])) { + $params['charset'] = $primary['charset']; } - return $config; + return $params; } /** diff --git a/src/Driver/IBMDB2/Connection.php b/src/Driver/IBMDB2/Connection.php index 26b288473d4..2c8783b2896 100644 --- a/src/Driver/IBMDB2/Connection.php +++ b/src/Driver/IBMDB2/Connection.php @@ -74,7 +74,13 @@ public function exec(string $sql): int|string throw StatementError::new(); } - return db2_num_rows($stmt); + $numRows = db2_num_rows($stmt); + + if ($numRows === false) { + throw StatementError::new(); + } + + return $numRows; } public function lastInsertId(): string diff --git a/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php b/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php index 231c9d47316..c584fb82c43 100644 --- a/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php +++ b/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php @@ -13,7 +13,7 @@ */ final class CannotCopyStreamToStream extends AbstractException { - /** @psalm-param array{message: string}|null $error */ + /** @psalm-param array{message: string, ...}|null $error */ public static function new(?array $error): self { $message = 'Could not copy source stream to temporary file'; diff --git a/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php b/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php index 63f7ca1e26f..d7646a0ce6d 100644 --- a/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php +++ b/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php @@ -13,7 +13,7 @@ */ final class CannotCreateTemporaryFile extends AbstractException { - /** @psalm-param array{message: string}|null $error */ + /** @psalm-param array{message: string, ...}|null $error */ public static function new(?array $error): self { $message = 'Could not create temporary file'; diff --git a/src/Driver/IBMDB2/Exception/PrepareFailed.php b/src/Driver/IBMDB2/Exception/PrepareFailed.php index 42df5e15c85..5344b6529df 100644 --- a/src/Driver/IBMDB2/Exception/PrepareFailed.php +++ b/src/Driver/IBMDB2/Exception/PrepareFailed.php @@ -13,7 +13,7 @@ */ final class PrepareFailed extends AbstractException { - /** @psalm-param array{message: string}|null $error */ + /** @psalm-param array{message: string, ...}|null $error */ public static function new(?array $error): self { if ($error === null) { diff --git a/src/Driver/IBMDB2/Result.php b/src/Driver/IBMDB2/Result.php index 1e7d3332d84..461f44aca4e 100644 --- a/src/Driver/IBMDB2/Result.php +++ b/src/Driver/IBMDB2/Result.php @@ -79,7 +79,13 @@ public function fetchFirstColumn(): array public function rowCount(): int { - return @db2_num_rows($this->statement); + $numRows = @db2_num_rows($this->statement); + + if ($numRows === false) { + throw StatementError::new($this->statement); + } + + return $numRows; } public function columnCount(): int diff --git a/src/Driver/Mysqli/Exception/ConnectionError.php b/src/Driver/Mysqli/Exception/ConnectionError.php index 98c6616f678..d2477fdba95 100644 --- a/src/Driver/Mysqli/Exception/ConnectionError.php +++ b/src/Driver/Mysqli/Exception/ConnectionError.php @@ -25,6 +25,6 @@ public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); - return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); + return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception); } } diff --git a/src/Driver/Mysqli/Exception/ConnectionFailed.php b/src/Driver/Mysqli/Exception/ConnectionFailed.php index e40bf476789..cb3bc64f740 100644 --- a/src/Driver/Mysqli/Exception/ConnectionFailed.php +++ b/src/Driver/Mysqli/Exception/ConnectionFailed.php @@ -30,6 +30,6 @@ public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); - return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); + return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception); } } diff --git a/src/Driver/Mysqli/Exception/InvalidCharset.php b/src/Driver/Mysqli/Exception/InvalidCharset.php index b6f3bb7bf1f..778ea645166 100644 --- a/src/Driver/Mysqli/Exception/InvalidCharset.php +++ b/src/Driver/Mysqli/Exception/InvalidCharset.php @@ -34,7 +34,7 @@ public static function upcast(mysqli_sql_exception $exception, string $charset): return new self( sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()), $p->getValue($exception), - (int) $exception->getCode(), + $exception->getCode(), $exception, ); } diff --git a/src/Driver/Mysqli/Exception/StatementError.php b/src/Driver/Mysqli/Exception/StatementError.php index edffd8183f2..991384cd915 100644 --- a/src/Driver/Mysqli/Exception/StatementError.php +++ b/src/Driver/Mysqli/Exception/StatementError.php @@ -25,6 +25,6 @@ public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); - return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); + return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception); } } diff --git a/src/Driver/OCI8/Connection.php b/src/Driver/OCI8/Connection.php index 49923828aa2..3652ca0eb85 100644 --- a/src/Driver/OCI8/Connection.php +++ b/src/Driver/OCI8/Connection.php @@ -78,7 +78,7 @@ public function quote(string $value): string * @throws Exception * @throws Parser\Exception */ - public function exec(string $sql): int + public function exec(string $sql): int|string { return $this->prepare($sql)->execute()->rowCount(); } diff --git a/src/DriverManager.php b/src/DriverManager.php index ad0f3462363..8b41cbbe1cc 100644 --- a/src/DriverManager.php +++ b/src/DriverManager.php @@ -27,18 +27,21 @@ * application_name?: string, * charset?: string, * dbname?: string, + * defaultTableOptions?: array, * driver?: key-of, * driverClass?: class-string, * driverOptions?: array, * host?: string, + * memory?: bool, * password?: string, * path?: string, * persistent?: bool, * port?: int, * serverVersion?: string, - * url?: string, + * sessionMode?: int, * user?: string, * unix_socket?: string, + * wrapperClass?: class-string, * } * @psalm-type Params = array{ * application_name?: string, @@ -49,9 +52,7 @@ * driverClass?: class-string, * driverOptions?: array, * host?: string, - * keepSlave?: bool, * keepReplica?: bool, - * master?: OverrideParams, * memory?: bool, * password?: string, * path?: string, @@ -60,8 +61,7 @@ * primary?: OverrideParams, * replica?: array, * serverVersion?: string, - * sharding?: array, - * slaves?: array, + * sessionMode?: int, * user?: string, * wrapperClass?: class-string, * unix_socket?: string, diff --git a/src/ExpandArrayParameters.php b/src/ExpandArrayParameters.php index fc2cb2aea61..253861cc2aa 100644 --- a/src/ExpandArrayParameters.php +++ b/src/ExpandArrayParameters.php @@ -26,7 +26,7 @@ final class ExpandArrayParameters implements Visitor /** @var list */ private array $convertedParameters = []; - /** @var array */ + /** @var array,string|ParameterType|Type> */ private array $convertedTypes = []; /** @@ -105,7 +105,7 @@ private function acceptParameter(int|string $key, mixed $value): void $this->appendTypedParameter($value, ArrayParameterType::toElementParameterType($type)); } - /** @return array */ + /** @return array,string|ParameterType|Type> */ public function getTypes(): array { return $this->convertedTypes; diff --git a/src/Logging/Driver.php b/src/Logging/Driver.php index cdd79f5bb3f..35acd393b03 100644 --- a/src/Logging/Driver.php +++ b/src/Logging/Driver.php @@ -45,10 +45,6 @@ private function maskPassword( $params['password'] = ''; } - if (isset($params['url'])) { - $params['url'] = ''; - } - return $params; } } diff --git a/src/Platforms/AbstractMySQLPlatform.php b/src/Platforms/AbstractMySQLPlatform.php index e5d1de1ed04..06f58813c03 100644 --- a/src/Platforms/AbstractMySQLPlatform.php +++ b/src/Platforms/AbstractMySQLPlatform.php @@ -474,7 +474,7 @@ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array } /** - * @return string[] + * @return list * * @throws Exception */ @@ -517,7 +517,7 @@ private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $inde /** * @param TableDiff $diff The table diff to gather the SQL for. * - * @return string[] + * @return list * * @throws Exception */ @@ -580,7 +580,7 @@ private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array /** * @param TableDiff $diff The table diff to gather the SQL for. * - * @return string[] + * @return list */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array { @@ -815,7 +815,7 @@ public function createSchemaManager(Connection $connection): MySQLSchemaManager } /** - * @param list $assets + * @param array $assets * * @return array * diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index 1ec5c7ff8cd..8077dd7c9c6 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -872,7 +872,7 @@ private function buildCreateTableSQL(Table $table, bool $createForeignKeys): arr } /** - * @param list $tables + * @param array
$tables * * @return list */ @@ -897,7 +897,7 @@ public function getCreateTablesSQL(array $tables): array } /** - * @param list
$tables + * @param array
$tables * * @return list */ @@ -1235,7 +1235,7 @@ public function getRenameTableSQL(string $oldName, string $newName): string return sprintf('ALTER TABLE %s RENAME TO %s', $oldName, $newName); } - /** @return string[] */ + /** @return list */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array { $tableNameSQL = $diff->getOldTable()->getQuotedName($this); @@ -1261,7 +1261,7 @@ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array return $sql; } - /** @return string[] */ + /** @return list */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array { $sql = []; @@ -1302,7 +1302,7 @@ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array * @param Index $index The definition of the index to rename to. * @param string $tableName The table to rename the given index on. * - * @return string[] The sequence of SQL statements for renaming the given index. + * @return list The sequence of SQL statements for renaming the given index. */ protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array { diff --git a/src/Platforms/DB2Platform.php b/src/Platforms/DB2Platform.php index 43e0d4a8af8..6e894b5e59e 100644 --- a/src/Platforms/DB2Platform.php +++ b/src/Platforms/DB2Platform.php @@ -350,10 +350,10 @@ public function getRenameTableSQL(string $oldName, string $newName): string /** * Gathers the table alteration SQL for a given column diff. * - * @param string $table The table to gather the SQL for. - * @param ColumnDiff $columnDiff The column diff to evaluate. - * @param string[] $sql The sequence of table alteration statements to fill. - * @param mixed[] $queryParts The sequence of column alteration clauses to fill. + * @param string $table The table to gather the SQL for. + * @param ColumnDiff $columnDiff The column diff to evaluate. + * @param list $sql The sequence of table alteration statements to fill. + * @param list $queryParts The sequence of column alteration clauses to fill. */ private function gatherAlterColumnSQL( string $table, diff --git a/src/Platforms/SQLitePlatform.php b/src/Platforms/SQLitePlatform.php index 2e22f1c6927..f2aa46a0485 100644 --- a/src/Platforms/SQLitePlatform.php +++ b/src/Platforms/SQLitePlatform.php @@ -702,7 +702,7 @@ private function replaceColumn(string $tableName, array $columns, string $column } /** - * @return string[]|false + * @return list|false * * @throws Exception */ diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php index 2eb58f262fa..c8d49ddfff9 100644 --- a/src/Query/QueryBuilder.php +++ b/src/Query/QueryBuilder.php @@ -76,6 +76,8 @@ class QueryBuilder /** * The counter of bound parameters used with {@see bindValue). + * + * @var int<0, max> */ private int $boundCounter = 0; @@ -345,7 +347,7 @@ public function getSQL(): string * ->setParameter('user_id', 1); * * - * @param int|string $key Parameter position or name + * @param int<0, max>|string $key Parameter position or name * * @return $this This QueryBuilder instance. */ diff --git a/src/Result.php b/src/Result.php index 2a9d233d6eb..11a537193e2 100644 --- a/src/Result.php +++ b/src/Result.php @@ -10,6 +10,8 @@ use Traversable; use function array_shift; +use function assert; +use function count; class Result { @@ -109,8 +111,10 @@ public function fetchAllKeyValue(): array $data = []; - foreach ($this->fetchAllNumeric() as [$key, $value]) { - $data[$key] = $value; + foreach ($this->fetchAllNumeric() as $row) { + assert(count($row) >= 2); + [$key, $value] = $row; + $data[$key] = $value; } return $data; @@ -174,7 +178,7 @@ public function iterateAssociative(): Traversable } /** - * {@inheritDoc} + * @return Traversable * * @throws Exception */ @@ -182,7 +186,10 @@ public function iterateKeyValue(): Traversable { $this->ensureHasKeyValue(); - foreach ($this->iterateNumeric() as [$key, $value]) { + foreach ($this->iterateNumeric() as $row) { + assert(count($row) >= 2); + [$key, $value] = $row; + yield $key => $value; } } diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 84f17251eb7..f973eaa3ed2 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -11,7 +11,9 @@ use Doctrine\DBAL\Types\Type; use function array_change_key_case; +use function array_key_exists; use function array_values; +use function assert; use function implode; use function is_string; use function preg_match; @@ -106,6 +108,8 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column $tableColumn['column_name'] = ''; } + assert(array_key_exists('data_default', $tableColumn)); + // Default values returned from database sometimes have trailing spaces. if (is_string($tableColumn['data_default'])) { $tableColumn['data_default'] = trim($tableColumn['data_default']); diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index ecd3ea67c73..28b3c904582 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -11,6 +11,7 @@ use Doctrine\DBAL\Types\Type; use function array_change_key_case; +use function array_key_exists; use function array_map; use function array_merge; use function assert; @@ -232,6 +233,9 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column $matches = []; + assert(array_key_exists('default', $tableColumn)); + assert(array_key_exists('complete_type', $tableColumn)); + if ($tableColumn['default'] !== null) { if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) { $tableColumn['default'] = $matches[1]; diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 45a80621d6f..b4f5c963216 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -50,11 +50,9 @@ private function getExecuteStatementMockConnection(): Connection { $driverMock = $this->createMock(Driver::class); - $platform = $this->getMockForAbstractClass(AbstractPlatform::class); - return $this->getMockBuilder(Connection::class) ->onlyMethods(['executeStatement']) - ->setConstructorArgs([['platform' => $platform], $driverMock]) + ->setConstructorArgs([[], $driverMock]) ->getMock(); } @@ -475,9 +473,7 @@ public function testCallConnectOnce(): void $driver->expects(self::once()) ->method('connect'); - $platform = $this->createMock(AbstractPlatform::class); - - $conn = new Connection(['platform' => $platform], $driver); + $conn = new Connection([], $driver); $conn->executeQuery('SELECT 1'); $conn->executeQuery('SELECT 2'); } diff --git a/tests/Driver/Middleware/AbstractDriverMiddlewareTest.php b/tests/Driver/Middleware/AbstractDriverMiddlewareTest.php index c44d336f133..d2e0e5c00cc 100644 --- a/tests/Driver/Middleware/AbstractDriverMiddlewareTest.php +++ b/tests/Driver/Middleware/AbstractDriverMiddlewareTest.php @@ -17,10 +17,10 @@ public function testConnect(): void $driver = $this->createMock(Driver::class); $driver->expects(self::once()) ->method('connect') - ->with(['foo' => 'bar']) + ->with(['host' => 'localhost']) ->willReturn($connection); - self::assertSame($connection, $this->createMiddleware($driver)->connect(['foo' => 'bar'])); + self::assertSame($connection, $this->createMiddleware($driver)->connect(['host' => 'localhost'])); } private function createMiddleware(Driver $driver): AbstractDriverMiddleware diff --git a/tests/DriverManagerTest.php b/tests/DriverManagerTest.php index 8dc9991d43b..dd4c6bdd4d7 100644 --- a/tests/DriverManagerTest.php +++ b/tests/DriverManagerTest.php @@ -17,7 +17,6 @@ use function array_merge; use function in_array; -use function is_array; /** @psalm-import-type Params from DriverManager */ class DriverManagerTest extends TestCase @@ -104,28 +103,21 @@ public function testValidDriverClass(): void } /** - * @param array|string $url - * @param array|false $expected + * @param Params $params + * @param array|false $expected * * @dataProvider databaseUrls */ - public function testDatabaseUrl(array|string $url, array|false $expected): void + public function testDatabaseUrl(string $url, array $params, array|false $expected): void { - if (is_array($url)) { - ['url' => $url] = $options = $url; - unset($options['url']); - } else { - $options = []; - } - - $parser = new DsnParser(['mysql' => 'pdo_mysql', 'sqlite' => 'pdo_sqlite']); - $options = array_merge($options, $parser->parse($url)); + $parser = new DsnParser(['mysql' => 'pdo_mysql', 'sqlite' => 'pdo_sqlite']); + $params = array_merge($params, $parser->parse($url)); if ($expected === false) { $this->expectException(Exception::class); } - $conn = DriverManager::getConnection($options); + $conn = DriverManager::getConnection($params); self::assertNotFalse($expected); @@ -140,7 +132,8 @@ public function testDatabaseUrl(array|string $url, array|false $expected): void } /** @psalm-return array, + * string, + * array, * array|false, * }> */ @@ -152,6 +145,7 @@ public function databaseUrls(): iterable return [ 'simple URL' => [ 'pdo-mysql://foo:bar@localhost/baz', + [], [ 'user' => 'foo', 'password' => 'bar', @@ -162,6 +156,7 @@ public function databaseUrls(): iterable ], 'simple URL with port' => [ 'pdo-mysql://foo:bar@localhost:11211/baz', + [], [ 'user' => 'foo', 'password' => 'bar', @@ -173,6 +168,7 @@ public function databaseUrls(): iterable ], 'sqlite relative URL with host' => [ 'pdo-sqlite://localhost/foo/dbname.sqlite', + [], [ 'path' => 'foo/dbname.sqlite', 'driver' => PDO\SQLite\Driver::class, @@ -180,6 +176,7 @@ public function databaseUrls(): iterable ], 'sqlite absolute URL with host' => [ 'pdo-sqlite://localhost//tmp/dbname.sqlite', + [], [ 'path' => '/tmp/dbname.sqlite', 'driver' => PDO\SQLite\Driver::class, @@ -187,6 +184,7 @@ public function databaseUrls(): iterable ], 'sqlite relative URL without host' => [ 'pdo-sqlite:///foo/dbname.sqlite', + [], [ 'path' => 'foo/dbname.sqlite', 'driver' => PDO\SQLite\Driver::class, @@ -194,6 +192,7 @@ public function databaseUrls(): iterable ], 'sqlite absolute URL without host' => [ 'pdo-sqlite:////tmp/dbname.sqlite', + [], [ 'path' => '/tmp/dbname.sqlite', 'driver' => PDO\SQLite\Driver::class, @@ -201,6 +200,7 @@ public function databaseUrls(): iterable ], 'sqlite memory' => [ 'pdo-sqlite:///:memory:', + [], [ 'memory' => true, 'driver' => PDO\SQLite\Driver::class, @@ -208,16 +208,15 @@ public function databaseUrls(): iterable ], 'sqlite memory with host' => [ 'pdo-sqlite://localhost/:memory:', + [], [ 'memory' => true, 'driver' => PDO\SQLite\Driver::class, ], ], 'params parsed from URL override individual params' => [ - [ - 'url' => 'pdo-mysql://foo:bar@localhost/baz', - 'password' => 'lulz', - ], + 'pdo-mysql://foo:bar@localhost/baz', + ['password' => 'lulz'], [ 'user' => 'foo', 'password' => 'bar', @@ -227,10 +226,8 @@ public function databaseUrls(): iterable ], ], 'params not parsed from URL but individual params are preserved' => [ - [ - 'url' => 'pdo-mysql://foo:bar@localhost/baz', - 'port' => 1234, - ], + 'pdo-mysql://foo:bar@localhost/baz', + ['port' => 1234], [ 'user' => 'foo', 'password' => 'bar', @@ -242,10 +239,12 @@ public function databaseUrls(): iterable ], 'query params from URL are used as extra params' => [ 'pdo-mysql://foo:bar@localhost/dbname?charset=UTF-8', + [], ['charset' => 'UTF-8'], ], 'simple URL with fallthrough scheme not defined in map' => [ 'sqlsrv://foo:bar@localhost/baz', + [], [ 'user' => 'foo', 'password' => 'bar', @@ -256,10 +255,12 @@ public function databaseUrls(): iterable ], 'simple URL with fallthrough scheme containing underscores fails' => [ 'pdo_mysql://foo:bar@localhost/baz', + [], false, ], 'simple URL with fallthrough scheme containing dashes works' => [ 'pdo-mysql://foo:bar@localhost/baz', + [], [ 'user' => 'foo', 'password' => 'bar', @@ -270,6 +271,7 @@ public function databaseUrls(): iterable ], 'simple URL with percent encoding' => [ 'pdo-mysql://foo%3A:bar%2F@localhost/baz+baz%40', + [], [ 'user' => 'foo:', 'password' => 'bar/', @@ -280,6 +282,7 @@ public function databaseUrls(): iterable ], 'simple URL with percent sign in password' => [ 'pdo-mysql://foo:bar%25bar@localhost/baz', + [], [ 'user' => 'foo', 'password' => 'bar%bar', @@ -291,14 +294,13 @@ public function databaseUrls(): iterable // DBAL-1234 'URL without scheme and without any driver information' => [ - ['url' => '//foo:bar@localhost/baz'], + '//foo:bar@localhost/baz', + [], false, ], 'URL without scheme but default driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'driver' => 'pdo_mysql', - ], + '//foo:bar@localhost/baz', + ['driver' => 'pdo_mysql'], [ 'user' => 'foo', 'password' => 'bar', @@ -308,10 +310,8 @@ public function databaseUrls(): iterable ], ], 'URL without scheme but custom driver' => [ - [ - 'url' => '//foo:bar@localhost/baz', - 'driverClass' => $driverClass, - ], + '//foo:bar@localhost/baz', + ['driverClass' => $driverClass], [ 'user' => 'foo', 'password' => 'bar', diff --git a/tests/Functional/BlobTest.php b/tests/Functional/BlobTest.php index 1d424a6107c..74392cfe412 100644 --- a/tests/Functional/BlobTest.php +++ b/tests/Functional/BlobTest.php @@ -63,9 +63,11 @@ public function testInsertNull(): void self::assertEquals(1, $ret); - [$clobValue, $blobValue] = $this->fetchRow(); - self::assertNull($clobValue); - self::assertNull($blobValue); + $row = $this->fetchRow(); + self::assertCount(2, $row); + + self::assertNull($row[0]); + self::assertNull($row[1]); } public function testInsertProcessesStream(): void diff --git a/tests/Functional/Platform/LengthExpressionTest.php b/tests/Functional/Platform/LengthExpressionTest.php index 1c15f5a1e8b..0ef8e911034 100644 --- a/tests/Functional/Platform/LengthExpressionTest.php +++ b/tests/Functional/Platform/LengthExpressionTest.php @@ -32,7 +32,7 @@ public function testLengthExpression(string $value, int $expected, bool $isMulti self::assertEquals($expected, $this->connection->fetchOne($query, [$value])); } - /** @return iterable */ + /** @return iterable */ public static function expressionProvider(): iterable { yield '1-byte' => ['Hello, world!', 13, false]; diff --git a/tests/Functional/Platform/RenameColumnTest.php b/tests/Functional/Platform/RenameColumnTest.php index 9f1d32fce59..1b321b19b3e 100644 --- a/tests/Functional/Platform/RenameColumnTest.php +++ b/tests/Functional/Platform/RenameColumnTest.php @@ -36,7 +36,7 @@ public function testColumnPositionRetainedAfterRenaming(string $columnName, stri self::assertEqualsIgnoringCase('c2', $columns[1]->getName()); } - /** @return iterable */ + /** @return iterable */ public static function columnNameProvider(): iterable { yield ['c1', 'c1_x']; diff --git a/tests/Functional/PrimaryReadReplicaConnectionTest.php b/tests/Functional/PrimaryReadReplicaConnectionTest.php index 10c56379fce..6b16eda0d49 100644 --- a/tests/Functional/PrimaryReadReplicaConnectionTest.php +++ b/tests/Functional/PrimaryReadReplicaConnectionTest.php @@ -54,9 +54,16 @@ private function createPrimaryReadReplicaConnection(bool $keepReplica = false): */ private function createPrimaryReadReplicaConnectionParams(bool $keepReplica = false): array { - $params = $this->connection->getParams(); - $params['primary'] = $params; - $params['replica'] = [$params, $params]; + $params = $instanceParams = $this->connection->getParams(); + + unset( + $instanceParams['keepReplica'], + $instanceParams['primary'], + $instanceParams['replica'], + ); + + $params['primary'] = $instanceParams; + $params['replica'] = [$instanceParams, $instanceParams]; $params['keepReplica'] = $keepReplica; $params['wrapperClass'] = PrimaryReadReplicaConnection::class; diff --git a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php index 4818fbde360..f924639662d 100644 --- a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php +++ b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php @@ -212,14 +212,14 @@ public function testListForeignKeys(): void $fkOptions = ['SET NULL', 'SET DEFAULT', 'NO ACTION', 'CASCADE', 'RESTRICT']; $foreignKeys = []; $fkTable = $this->getTestTable('test_create_fk1'); - for ($i = 0; $i < count($fkOptions); $i++) { + foreach ($fkOptions as $i => $fkOption) { $fkTable->addColumn('foreign_key_test' . $i, Types::INTEGER); $foreignKeys[] = new ForeignKeyConstraint( ['foreign_key_test' . $i], 'test_create_fk2', ['id'], 'foreign_key_test' . $i . '_fk', - ['onDelete' => $fkOptions[$i]], + ['onDelete' => $fkOption], ); } diff --git a/tests/Logging/MiddlewareTest.php b/tests/Logging/MiddlewareTest.php index 62db183e266..27a4e56a597 100644 --- a/tests/Logging/MiddlewareTest.php +++ b/tests/Logging/MiddlewareTest.php @@ -33,18 +33,16 @@ public function setUp(): void public function testConnectAndDisconnect(): void { $this->driver->connect([ - 'username' => 'admin', + 'user' => 'admin', 'password' => 'Passw0rd!', - 'url' => 'mysql://user:secret@localhost/mydb', ]); self::assertTrue($this->logger->hasInfo([ 'message' => 'Connecting with parameters {params}', 'context' => [ 'params' => [ - 'username' => 'admin', + 'user' => 'admin', 'password' => '', - 'url' => '', ], ], ])); diff --git a/tests/Schema/MySQLInheritCharsetTest.php b/tests/Schema/MySQLInheritCharsetTest.php index b141d938687..e5eb64ea495 100644 --- a/tests/Schema/MySQLInheritCharsetTest.php +++ b/tests/Schema/MySQLInheritCharsetTest.php @@ -16,8 +16,6 @@ use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\TestCase; -use function array_merge; - /** @psalm-import-type Params from DriverManager */ class MySQLInheritCharsetTest extends TestCase { @@ -70,7 +68,6 @@ private function getTableOptionsForOverride(array $params = []): array $driverMock = $this->createMock(Driver::class); $platform = new MySQLPlatform(); - $params = array_merge(['platform' => $platform], $params); $conn = new Connection($params, $driverMock, new Configuration()); $manager = new MySQLSchemaManager($conn, $platform); diff --git a/tests/TestUtil.php b/tests/TestUtil.php index 3f8a6f5316a..354ce38ef87 100644 --- a/tests/TestUtil.php +++ b/tests/TestUtil.php @@ -22,6 +22,7 @@ use function array_keys; use function array_map; use function array_values; +use function assert; use function extension_loaded; use function file_exists; use function implode; @@ -34,6 +35,8 @@ /** * TestUtil is a class with static utility methods used during tests. + * + * @psalm-import-type Params from DriverManager */ class TestUtil { @@ -61,12 +64,14 @@ class TestUtil */ public static function getConnection(): Connection { - if (self::hasRequiredConnectionParams() && ! self::$initialized) { + $params = self::getConnectionParams(); + + if (empty($params['memory']) && ! self::$initialized) { self::initializeDatabase(); self::$initialized = true; } - $params = self::getConnectionParams(); + assert(isset($params['driver'])); return DriverManager::getConnection( $params, @@ -74,19 +79,23 @@ public static function getConnection(): Connection ); } - /** @return mixed[] */ + /** @return Params */ public static function getConnectionParams(): array { - if (self::hasRequiredConnectionParams()) { - return self::getTestConnectionParameters(); + $params = self::getTestConnectionParameters(); + + if (isset($params['driver'])) { + return $params; } - return self::getFallbackConnectionParams(); - } + if (! extension_loaded('pdo_sqlite')) { + Assert::markTestSkipped('PDO SQLite extension is not loaded'); + } - private static function hasRequiredConnectionParams(): bool - { - return isset($GLOBALS['db_driver']); + return [ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ]; } private static function initializeDatabase(): void @@ -114,15 +123,21 @@ private static function initializeDatabase(): void $testConn->close(); } else { if (! $platform instanceof OraclePlatform) { + if (! isset($testConnParams['dbname'])) { + throw new InvalidArgumentException( + 'You must have a database configured in your connection.', + ); + } + $dbname = $testConnParams['dbname']; } else { - $dbname = $testConnParams['user']; - } + if (! isset($testConnParams['user'])) { + throw new InvalidArgumentException( + 'You must have a user configured in your connection.', + ); + } - if ($dbname === null) { - throw new InvalidArgumentException( - 'You must have a database configured in your connection.', - ); + $dbname = $testConnParams['user']; } $sm = $privConn->createSchemaManager(); @@ -138,19 +153,6 @@ private static function initializeDatabase(): void $privConn->close(); } - /** @return mixed[] */ - private static function getFallbackConnectionParams(): array - { - if (! extension_loaded('pdo_sqlite')) { - Assert::markTestSkipped('PDO SQLite extension is not loaded'); - } - - return [ - 'driver' => 'pdo_sqlite', - 'memory' => true, - ]; - } - private static function createConfiguration(string $driver): Configuration { $configuration = new Configuration(); @@ -171,7 +173,7 @@ private static function createConfiguration(string $driver): Configuration return $configuration; } - /** @return mixed[] */ + /** @return Params */ private static function getPrivilegedConnectionParameters(): array { if (isset($GLOBALS['tmpdb_driver'])) { @@ -184,7 +186,7 @@ private static function getPrivilegedConnectionParameters(): array return $parameters; } - /** @return mixed[] */ + /** @return Params */ private static function getTestConnectionParameters(): array { return self::mapConnectionParameters($GLOBALS, 'db_'); @@ -193,7 +195,7 @@ private static function getTestConnectionParameters(): array /** * @param array $configuration * - * @return array + * @return Params */ private static function mapConnectionParameters(array $configuration, string $prefix): array { @@ -254,7 +256,10 @@ public static function getPrivilegedConnection(): Connection public static function isDriverOneOf(string ...$names): bool { - return in_array(self::getConnectionParams()['driver'], $names, true); + $params = self::getConnectionParams(); + assert(isset($params['driver'])); + + return in_array($params['driver'], $names, true); } /**