diff --git a/UPGRADE.md b/UPGRADE.md index 56fd9f39179..8a0d4a4c3c9 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,11 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + # Upgrade to 4.0 ## Removed `AbstractPlatform::getReservedKeywordsClass()` @@ -301,11 +309,20 @@ Use `AbstractSchemaManager::listSchemaNames()` instead. `PostgreSQLSchemaManager::getExistingSchemaSearchPaths()` and `::determineExistingSchemaSearchPaths()` have been marked internal. +## `OracleSchemaManager` methods marked internal. + +`OracleSchemaManager::dropAutoincrement()` has been marked internal. + ## Deprecated `AbstractPlatform::getReservedKeywordsClass()` Instead of implementing `getReservedKeywordsClass()`, `AbstractPlatform` subclasses should implement `createReservedKeywordsList()`. +## Deprecated `ReservedWordsCommand::setKeywordListClass()` + +The usage of `ReservedWordsCommand::setKeywordListClass()` has been deprecated. To add or replace a keyword list, +use `setKeywordList()` instead. + ## Deprecated `$driverOptions` argument of `PDO\Statement::bindParam()` and `PDO\SQLSrv\Statement::bindParam()` The usage of the `$driverOptions` argument of `PDO\Statement::bindParam()` and `PDO\SQLSrv\Statement::bindParam()` is deprecated. @@ -321,6 +338,23 @@ Use `Connection::createSchemaManager()` instead. The usage of `Connection::$_expr` and `Connection::getExpressionBuilder()` is deprecated. Use `Connection::createExpressionBuilder()` instead. +## Deprecated `QueryBuilder::execute()` + +The usage of `QueryBuilder::execute()` is deprecated. Use either `QueryBuilder::executeQuery()` or +`QueryBuilder::executeStatement()`, depending on whether the queryBuilder is a query (SELECT) or a statement (INSERT, +UPDATE, DELETE). + +You might also consider the use of the new shortcut methods, such as: + +- `fetchAllAssociative()` +- `fetchAllAssociativeIndexed()` +- `fetchAllKeyValue()` +- `fetchAllNumeric()` +- `fetchAssociative()` +- `fetchFirstColumn()` +- `fetchNumeric()` +- `fetchOne()` + # Upgrade to 3.0 ## BC BREAK: leading colon in named parameter names not supported diff --git a/composer.json b/composer.json index 28bca654a41..1dfae1bb9d7 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,7 @@ "phpstan/phpstan-strict-rules": "0.12.2", "phpunit/phpunit": "9.5.0", "psalm/plugin-phpunit": "0.13.0", + "squizlabs/php_codesniffer": "3.6.0", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", "vimeo/psalm": "4.6.4" }, diff --git a/phpcs.xml.dist b/phpcs.xml.dist index c8f0c9c3307..7b4cd3b5075 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -99,8 +99,7 @@ tests/Functional/DataAccess/FetchClass.php - - + - src/SQLParserUtils.php src/Tools/Dumper.php tests/Driver/StatementIteratorTest.php tests/Tools/DumperTest.php + + + src/SQLParserUtils.php + + src/Driver/SQLSrv/Result.php diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php index 78c7d1766b3..7001e59a593 100644 --- a/src/Query/QueryBuilder.php +++ b/src/Query/QueryBuilder.php @@ -348,9 +348,35 @@ public function fetchFirstColumn(): array return $this->connection->fetchFirstColumn($this->getSQL(), $this->params, $this->paramTypes); } + /** + * Executes an SQL query (SELECT) and returns a Result. + * + * @throws Exception + */ + public function executeQuery(): Result + { + return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes); + } + + /** + * Executes an SQL statement and returns the number of affected rows. + * + * Should be used for INSERT, UPDATE and DELETE + * + * @return int The number of affected rows. + * + * @throws Exception + */ + public function executeStatement(): int + { + return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); + } + /** * Executes this query using the bound parameters and their types. * + * @deprecated Use {@link executeQuery()} or {@link executeStatement()} instead. + * * @return Result|int * * @throws Exception @@ -358,9 +384,21 @@ public function fetchFirstColumn(): array public function execute() { if ($this->type === self::SELECT) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4578', + 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.' + ); + return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes); } + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4578', + 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeStatement() for SQL statements instead.' + ); + return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); } diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 77142322d83..6c862c0004b 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -265,6 +265,8 @@ public function createDatabase(string $database): void } /** + * @internal The method should be only used from within the OracleSchemaManager class hierarchy. + * * @throws Exception */ public function dropAutoincrement(string $table): bool diff --git a/src/Tools/Console/Command/ReservedWordsCommand.php b/src/Tools/Console/Command/ReservedWordsCommand.php index 44ac850f250..9f4591c8ead 100644 --- a/src/Tools/Console/Command/ReservedWordsCommand.php +++ b/src/Tools/Console/Command/ReservedWordsCommand.php @@ -19,6 +19,7 @@ use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords; use Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords; use Doctrine\DBAL\Tools\Console\ConnectionProvider; +use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -35,19 +36,8 @@ class ReservedWordsCommand extends Command { - /** @var array> */ - private $keywordListClasses = [ - 'db2' => DB2Keywords::class, - 'mysql' => MySQLKeywords::class, - 'mysql57' => MySQL57Keywords::class, - 'mysql80' => MySQL80Keywords::class, - 'mariadb102' => MariaDb102Keywords::class, - 'oracle' => OracleKeywords::class, - 'pgsql' => PostgreSQL94Keywords::class, - 'pgsql100' => PostgreSQL100Keywords::class, - 'sqlite' => SQLiteKeywords::class, - 'sqlserver' => SQLServer2012Keywords::class, - ]; + /** @var array */ + private $keywordLists; /** @var ConnectionProvider */ private $connectionProvider; @@ -56,6 +46,27 @@ public function __construct(ConnectionProvider $connectionProvider) { parent::__construct(); $this->connectionProvider = $connectionProvider; + + $this->keywordLists = [ + 'db2' => new DB2Keywords(), + 'mariadb102' => new MariaDb102Keywords(), + 'mysql' => new MySQLKeywords(), + 'mysql57' => new MySQL57Keywords(), + 'mysql80' => new MySQL80Keywords(), + 'oracle' => new OracleKeywords(), + 'pgsql' => new PostgreSQL94Keywords(), + 'pgsql100' => new PostgreSQL100Keywords(), + 'sqlite' => new SQLiteKeywords(), + 'sqlserver' => new SQLServer2012Keywords(), + ]; + } + + /** + * Add or replace a keyword list. + */ + public function setKeywordList(string $name, KeywordList $keywordList): void + { + $this->keywordLists[$name] = $keywordList; } /** @@ -65,7 +76,14 @@ public function __construct(ConnectionProvider $connectionProvider) */ public function setKeywordListClass(string $name, string $class): void { - $this->keywordListClasses[$name] = $class; + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4510', + 'ReservedWordsCommand::setKeywordListClass() is deprecated,' + . ' use ReservedWordsCommand::setKeywordList() instead.' + ); + + $this->keywordLists[$name] = new $class(); } protected function configure(): void @@ -86,8 +104,7 @@ protected function configure(): void Checks if the current database contains tables and columns with names that are identifiers in this dialect or in other SQL dialects. -By default SQLite, MySQL, PostgreSQL, Microsoft SQL Server and Oracle -keywords are checked: +By default all supported platform keywords are checked: %command.full_name% @@ -98,17 +115,16 @@ protected function configure(): void The following keyword lists are currently shipped with Doctrine: + * db2 + * mariadb102 * mysql * mysql57 * mysql80 - * mariadb102 + * oracle * pgsql * pgsql100 * sqlite - * oracle * sqlserver - * sqlserver2012 - * db2 (Not checked by default) EOT ); } @@ -131,21 +147,20 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (count($keywordLists) === 0) { - $keywordLists = array_keys($this->keywordListClasses); + $keywordLists = array_keys($this->keywordLists); } $keywords = []; foreach ($keywordLists as $keywordList) { - if (! isset($this->keywordListClasses[$keywordList])) { + if (! isset($this->keywordLists[$keywordList])) { throw new InvalidArgumentException(sprintf( 'There exists no keyword list with name "%s". Known lists: %s', $keywordList, - implode(', ', array_keys($this->keywordListClasses)) + implode(', ', array_keys($this->keywordLists)) )); } - $class = $this->keywordListClasses[$keywordList]; - $keywords[] = new $class(); + $keywords[] = $this->keywordLists[$keywordList]; } $output->write( diff --git a/tests/Platforms/SqlitePlatformTest.php b/tests/Platforms/SqlitePlatformTest.php index 39298f880a6..7e08466a5c8 100644 --- a/tests/Platforms/SqlitePlatformTest.php +++ b/tests/Platforms/SqlitePlatformTest.php @@ -574,7 +574,7 @@ protected function getQuotedAlterTableChangeColumnLengthSQL(): array public function testAlterTableRenameIndexInSchema(): void { self::markTestIncomplete( - 'Test currently produces broken SQL due to SQLitePlatform::getAlterTable being broken ' . + 'Test currently produces broken SQL due to SQLitePlatform::getAlterTable() being broken ' . 'when used with schemas.' ); } @@ -582,7 +582,7 @@ public function testAlterTableRenameIndexInSchema(): void public function testQuotesAlterTableRenameIndexInSchema(): void { self::markTestIncomplete( - 'Test currently produces broken SQL due to SQLitePlatform::getAlterTable being broken ' . + 'Test currently produces broken SQL due to SQLitePlatform::getAlterTable() being broken ' . 'when used with schemas.' ); } diff --git a/tests/Query/QueryBuilderTest.php b/tests/Query/QueryBuilderTest.php index 41be462992e..c14a8ef7fa0 100644 --- a/tests/Query/QueryBuilderTest.php +++ b/tests/Query/QueryBuilderTest.php @@ -9,6 +9,7 @@ use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Query\QueryException; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\MockObject\MockObject; @@ -1197,4 +1198,72 @@ public static function fetchProvider(): iterable 'SELECT id, username FROM user WHERE password = :password AND username != :username AND id != :id', ]; } + + /** + * @param list|array $parameters + * @param array|array $parameterTypes + * + * @dataProvider fetchProvider + */ + public function testExecuteQuery( + string $select, + string $from, + string $where, + array $parameters, + array $parameterTypes, + string $expectedSql + ): void { + $qb = new QueryBuilder($this->conn); + $mockedResult = $this->createMock(Result::class); + + $this->conn->expects(self::once()) + ->method('executeQuery') + ->with($expectedSql, $parameters, $parameterTypes) + ->willReturn($mockedResult); + + $results = $qb->select($select) + ->from($from) + ->where($where) + ->setParameters($parameters, $parameterTypes) + ->executeQuery(); + + self::assertSame( + $mockedResult, + $results + ); + } + + public function testExecuteStatement(): void + { + $qb = new QueryBuilder($this->conn); + $mockedResult = 123; + $expectedSql = 'UPDATE users SET foo = ?, bar = ? WHERE bar = 1'; + + $parameters = [ + 'foo' => 'jwage', + 'bar' => false, + ]; + + $parameterTypes = [ + 'foo' => Types::STRING, + 'bar' => Types::BOOLEAN, + ]; + + $this->conn->expects(self::once()) + ->method('executeStatement') + ->with($expectedSql, $parameters, $parameterTypes) + ->willReturn($mockedResult); + + $results = $qb->update('users') + ->set('foo', '?') + ->set('bar', '?') + ->where('bar = 1') + ->setParameters($parameters, $parameterTypes) + ->executeStatement(); + + self::assertSame( + $mockedResult, + $results + ); + } }