diff --git a/.gitattributes b/.gitattributes index 97a6be4d775..245c7f56ca3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ /.appveyor.yml export-ignore +/ci export-ignore /composer.lock export-ignore /docs export-ignore /.doctrine-project.json export-ignore diff --git a/UPGRADE.md b/UPGRADE.md index e9538ddbc4a..12155af5a9e 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -254,6 +254,10 @@ The Doctrine\DBAL\Version class is no longer available: please refrain from chec # Upgrade to 3.0 +## BC BREAK: leading colon in named parameter names not supported + +The usage of the colon prefix when binding named parameters is no longer supported. + ## BC BREAK `Doctrine\DBAL\Abstraction\Result` removed The `Doctrine\DBAL\Abstraction\Result` interface is removed. Use the `Doctrine\DBAL\Result` class instead. @@ -264,23 +268,22 @@ The `Doctrine\DBAL\Types\Type::getDefaultLength()` method has been removed as it ## BC BREAK: `Doctrine\DBAL\DBALException` class renamed -The `Doctrine\DBAL\DBALException` class has been renamed to `Doctrine\DBAL\Exception`. +The `Doctrine\DBAL\DBALException` class has been renamed to `Doctrine\DBAL\Exception`. -## BC BREAK: Doctrine\DBAL\Schema\Table constructor new parameter +## BC BREAK: `Doctrine\DBAL\Schema\Table` constructor new parameter Deprecated parameter `$idGeneratorType` removed and added a new parameter `$uniqueConstraints`. -Constructor changed from: - -`__construct($name, array $columns = [], array $indexes = [], array $fkConstraints = [], $idGeneratorType = 0, array $options = [])` - -To the new constructor: +Constructor changed like so: -`__construct($name, array $columns = [], array $indexes = [], array $uniqueConstraints = [], array $fkConstraints = [], array $options = [])` +```diff +- __construct($name, array $columns = [], array $indexes = [], array $fkConstraints = [], $idGeneratorType = 0, array $options = []) ++ __construct($name, array $columns = [], array $indexes = [], array $uniqueConstraints = [], array $fkConstraints = [], array $options = []) +``` ## BC BREAK: change in the behavior of `SchemaManager::dropDatabase()` When dropping a database, the DBAL no longer attempts to kill the client sessions that use the database. -It's the responsibility of the operator to make sure that the database is not being used. +It's the responsibility of the operator to make sure that the database is not being used. ## BC BREAK: removed `Synchronizer` package @@ -298,14 +301,14 @@ The following methods of the `Connection` class have been removed: The wrapper-level `Connection` and `Statement` classes no longer implement the corresponding driver-level interfaces. -## BC BREAK: Removed DBALException factory methods +## BC BREAK: Removed `DBALException` factory methods -The following factory methods of the DBALException class have been removed: +The following factory methods of the `DBALException` class have been removed: 1. `DBALException::invalidPlatformSpecified()`. 2. `DBALException::invalidPdoInstance()`. -## BC BREAK: PDO-based driver classes are moved under the PDO namespace +## BC BREAK: PDO-based driver classes are moved under the `PDO` namespace The following classes have been renamed: @@ -331,7 +334,7 @@ The following classes have been renamed: ## BC BREAK: Changes in wrapper-level exceptions -1. `DBALException::invalidTableName()` has been replaced with the `InvalidTableName` class. +`DBALException::invalidTableName()` has been replaced with the `InvalidTableName` class. ## BC BREAK: Changes in driver-level exception handling @@ -339,9 +342,9 @@ The following classes have been renamed: 2. The `driverException()` and `driverExceptionDuringQuery()` factory methods have been removed from the `DBALException` class. 3. Non-driver exceptions (e.g. exceptions of type `Error`) are no longer wrapped in a `DBALException`. -## BC BREAK: More driver-level methods are allowed to throw a Driver\Exception. +## BC BREAK: More driver-level methods are allowed to throw a `Driver\Exception`. -The following driver-level methods are allowed to throw a Driver\Exception: +The following driver-level methods are allowed to throw a `Driver\Exception`: - `Connection::prepare()` - `Connection::lastInsertId()` @@ -454,7 +457,7 @@ The `Doctrine\DBAL\Driver::getName()` has been removed. ## BC BREAK changes the `Driver::connect()` signature -The method no longer accepts the `$username`, `$password` and `$driverOptions` arguments. The corresponding values are expected to be passed as the "user", "password" and "driver_options" keys of the `$params` argument respectively. +The method no longer accepts the `$username`, `$password` and `$driverOptions` arguments. The corresponding values are expected to be passed as the `"user"`, `"password"` and `"driver_options"` keys of the `$params` argument respectively. ## Removed `MasterSlaveConnection` @@ -538,15 +541,15 @@ The following classes have been removed: DBAL now requires MariaDB 10.1 or newer, support for unmaintained versions has been dropped. If you are using any of the legacy versions, you have to upgrade to a newer MariaDB version (10.1+ is recommended). -## BC BREAK: The ServerInfoAwareConnection interface now extend Connection +## BC BREAK: The `ServerInfoAwareConnection` interface now extends `Connection` All implementations of the `ServerInfoAwareConnection` interface have to implement the methods defined in the `Connection` interface as well. -## BC BREAK: VersionAwarePlatformDriver interface now extends Driver +## BC BREAK: `VersionAwarePlatformDriver` interface now extends `Driver` All implementations of the `VersionAwarePlatformDriver` interface have to implement the methods defined in the `Driver` interface as well. -## BC BREAK: Removed MsSQLKeywords class +## BC BREAK: Removed `MsSQLKeywords` class The `Doctrine\DBAL\Platforms\MsSQLKeywords` class has been removed. Please use `Doctrine\DBAL\Platforms\SQLServerPlatform `instead. @@ -575,7 +578,7 @@ The following classes have been removed: The `AbstractSQLServerDriver` class and its subclasses no longer implement the `VersionAwarePlatformDriver` interface. -## BC BREAK: Removed Doctrine\DBAL\Version +## BC BREAK: Removed `Doctrine\DBAL\Version` The `Doctrine\DBAL\Version` class is no longer available: please refrain from checking the DBAL version at runtime. @@ -591,36 +594,40 @@ In order to share the same `PDO` instances between DBAL and other components, in Before: - use Doctrine\DBAL\Portability\Connection; +```php +use Doctrine\DBAL\Portability\Connection; - $params = array( - 'wrapperClass' => Connection::class, - 'fetch_case' => PDO::CASE_LOWER, - ); +$params = array( + 'wrapperClass' => Connection::class, + 'fetch_case' => PDO::CASE_LOWER, +); - $stmt->bindValue(1, 1, PDO::PARAM_INT); - $stmt->fetchAll(PDO::FETCH_COLUMN); +$stmt->bindValue(1, 1, PDO::PARAM_INT); +$stmt->fetchAll(PDO::FETCH_COLUMN); +``` After: - use Doctrine\DBAL\ColumnCase; - use Doctrine\DBAL\FetchMode; - use Doctrine\DBAL\ParameterType; - use Doctrine\DBAL\Portability\Connection; +```php +use Doctrine\DBAL\ColumnCase; +use Doctrine\DBAL\FetchMode; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Portability\Connection; - $params = array( - 'wrapperClass' => Connection::class, - 'fetch_case' => ColumnCase::LOWER, - ); +$params = array( + 'wrapperClass' => Connection::class, + 'fetch_case' => ColumnCase::LOWER, +); - $stmt->bindValue(1, 1, ParameterType::INTEGER); - $stmt->fetchAll(FetchMode::COLUMN); +$stmt->bindValue(1, 1, ParameterType::INTEGER); +$stmt->fetchAll(FetchMode::COLUMN); +``` ## BC BREAK: Removed Drizzle support The Drizzle project is abandoned and is therefore not supported by Doctrine DBAL anymore. -## BC BREAK: Removed dbal:import CLI command +## BC BREAK: Removed `dbal:import` CLI command The `dbal:import` CLI command has been removed since it only worked with PDO-based drivers by relying on a non-documented behavior of the extension, and it was impossible to make it work with other drivers. Please use other database client applications for import, e.g.: @@ -629,8 +636,74 @@ Please use other database client applications for import, e.g.: * For PostgreSQL: `psql [dbname] < data.sql`. * For SQLite: `sqlite3 /path/to/file.db < data.sql`. +## BC BREAK: Changed signature of `ExceptionConverter::convert()` + +Before: + +```php +public function convert(string $message, Doctrine\DBAL\Driver\Exception $exception): DriverException +``` + +After: + +```php +public function convert(Doctrine\DBAL\Driver\Exception $exception, ?Doctrine\DBAL\Query $query): DriverException +``` + +## BC Break: The `DriverException` constructor is now internal + +The constructor of `Doctrine\DBAL\Exception\DriverException` is now `@internal`. + # Upgrade to 2.12 +## Deprecated non-zero based positional parameter keys + +The usage of one-based and other non-zero-based keys when binding positional parameters is deprecated. + +It is recommended to not use any array keys so that the value of the parameter array complies with the [`list<>`](https://psalm.dev/docs/annotating_code/type_syntax/array_types/) type constraint. + +```php +// This is valid (implicit zero-based parameter indexes) +$conn->fetchNumeric('SELECT ?, ?', [1, 2]); + +// This is invalid (one-based parameter indexes) +$conn->fetchNumeric('SELECT ?, ?', [1 => 1, 2 => 2]); + +// This is invalid (arbitrary parameter indexes) +$conn->fetchNumeric('SELECT ?, ?', [-31 => 1, 5 => 2]); + +// This is invalid (non-sequential parameter indexes) +$conn->fetchNumeric('SELECT ?, ?', [0 => 1, 3 => 2]); +``` + +## Deprecated skipping prepared statement parameters + +Some underlying drivers currently allow skipping prepared statement parameters. For instance: + +```php +$conn->fetchOne('SELECT ?'); +// NULL +``` + +This behavior should not be relied upon and may change in future versions. + +## Deprecated colon prefix for prepared statement parameters + +The usage of the colon prefix when binding named parameters is deprecated. + +```php +$sql = 'SELECT * FROM users WHERE name = :name OR username = :username'; +$stmt = $conn->prepare($sql); + +// The usage of the leading colon is deprecated +$stmt->bindValue(':name', $name); + +// Only the parameter name should be passed +$stmt->bindValue('username', $username); + +$stmt->execute(); +``` + ## PDO signature changes with php 8 In php 8.0, the method signatures of two PDO classes which are extended by DBAL have changed. This affects the following classes: @@ -707,7 +780,7 @@ The following PDO-related classes outside of the PDO namespace have been depreca 3. `prefersSequences()`. 4. `supportsForeignKeyOnUpdate()`. -##`ServerInfoAwareConnection::requiresQueryForServerVersion()` is deprecated. +## `ServerInfoAwareConnection::requiresQueryForServerVersion()` is deprecated. The `ServerInfoAwareConnection::requiresQueryForServerVersion()` method has been deprecated as an implementation detail which is the same for almost all supported drivers. diff --git a/composer.lock b/composer.lock index afc7bac53cd..2de81f9b12d 100644 --- a/composer.lock +++ b/composer.lock @@ -3176,16 +3176,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.5", + "version": "3.5.8", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", - "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", "shasum": "" }, "require": { @@ -3223,7 +3223,12 @@ "phpcs", "standards" ], - "time": "2020-04-17T01:09:41+00:00" + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2020-10-23T02:01:07+00:00" }, { "name": "symfony/console", @@ -3805,5 +3810,5 @@ "platform-overrides": { "php": "7.3.0" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/docs/en/reference/data-retrieval-and-manipulation.rst b/docs/en/reference/data-retrieval-and-manipulation.rst index 7e85e3c3a82..7902584c3de 100644 --- a/docs/en/reference/data-retrieval-and-manipulation.rst +++ b/docs/en/reference/data-retrieval-and-manipulation.rst @@ -91,9 +91,11 @@ are then replaced by their actual values in a second step (execute). $stmt->bindValue(1, $id); $stmt->execute(); -Placeholders in prepared statements are either simple positional question marks (?) or named labels starting with -a double-colon (:name1). You cannot mix the positional and the named approach. The approach -using question marks is called positional, because the values are bound in order from left to right +Placeholders in prepared statements are either simple positional question marks (``?``) or named labels starting with +a colon (e.g. ``:name1``). You cannot mix the positional and the named approach. You have to bind a parameter +to each placeholder. + +The approach using question marks is called positional, because the values are bound in order from left to right to any question mark found in the previously prepared SQL query. That is why you specify the position of the variable to bind into the ``bindValue()`` method: diff --git a/docs/en/reference/testing.rst b/docs/en/reference/testing.rst new file mode 100644 index 00000000000..526d6b69a20 --- /dev/null +++ b/docs/en/reference/testing.rst @@ -0,0 +1,123 @@ +Testing Guidelines +=================== + +To ensure high quality, all components of the Doctrine DBAL library are extensively covered with tests. + +Having the code covered with tests and running all tests against each individual code change helps prevent +breakages of the library logic when its code changes. + +Additionally, when code changes are accompanied by new tests, the tests: + +1. Help understand what problem the given code change is trying to solve. +2. Make sure that the problem being solved needs to be solved in the DBAL. +3. Document the proper usage of the DBAL APIs. + +Requirements +------------ + +1. Each pull request that adds new or changes the existing logic must have tests. + + .. note:: + + Modifications to the keyword lists under the ``Doctrine\DBAL\Platforms\Keywords`` namespace + don't have to be covered with tests. + +2. The test that covers certain logic must fail without this logic implemented. + +Types of Tests +-------------- + +Doctrine DBAL primarily uses unit and integration tests. + +Unit Tests +~~~~~~~~~~ + +Unit tests are meant to cover the logic of a given unit (e.g. a class or a method) including the logic +of its interaction with other units. In this case, the other units could be mocked. + +Unit tests are most welcomed for testing the logic that the DBAL itself defines (e.g. logging, caching, data types). + +In this case, the DBAL is the source of truth about what this logic is and the test plays the role of its description. + +Integration Tests +~~~~~~~~~~~~~~~~~ + +Integration (a.k.a. functional) tests are required when the behavior under test is dictated by the logic +defined outside of the DBAL. It could be: + +- The underlying database platform. +- The underlying database driver. +- SQL syntax and the standard as such. + +It is important to have integration tests for the cases above. Unlike unit tests, they make the external components +the source of truth and help make sure that the logic implemented in the DBAL is correct even if the external components +change (e.g. a new version of a database platform is supported). + +When are Integration Tests not Required? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some cases cannot be reproduced with the existing integration testing suite. It could be the scenarios that involve +multiple concurrent database connections, transactions, locking, performance-related issues, etc. + +In such cases, it is still important that a pull request fixing the issues is accompanied by a free-form reproducer +that demonstrates the issue being fixed. + +Recommendations on Writing Tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests in Doctrine DBAL are located under the ``tests`` directory and implemented on top of PHPUnit. Use its +`documentation `_ to get started. + +Writing Integration Tests +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Integration tests are located under the ``tests/Doctrine/Tests/DBAL/Functional`` directory. Unlike unit tests, +they require a real database connection to test their logic against. + +It is recommended to use ``Doctrine\DBAL\Tests\FunctionalTestCase`` as the base class for integration tests. +Based on the configuration, it will automatically create and connect to the test database. + +Data Fixtures in Integration Tests +++++++++++++++++++++++++++++++++++ + +To test selecting and fetching data from the database, the test may create the necessary schema and populate it +with the test data. To create database tables, instead of checking if the table exists, it is recommended +to use ``AbstractSchemaManager::dropAndCreateTable()``. This way, the table will be dropped and created every time +providing better isolation between the test runs. + +Testing Different Database Platforms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Although most of the issues are originally discovered on a specific database platform, +the integration tests for all issues should be implemented by default at the database abstraction level +and run against all the platforms that support the API being tested. + +This allows us to ensure that the same scenario that was found failing on one platform also works on others. Or otherwise, +the same issue could be reproduced on the platforms where it wasn't originally tested. + +If the newly added test fails on other platforms, and fixing it is out of the scope, the test can be explicitly marked +as incomplete which will identify the issue. + +Examples of such tests could be found under the ``Doctrine\DBAL\Tests\Functional\Platform`` namespace. + +Using Unit and Integration Tests Together +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For example, the ``AbstractPlatform::modifyLimitQuery()`` method has both unit and integration tests. + +1. Unit test cases for each platform (``Doctrine\DBAL\Tests\Platforms\*PlatformTest``) have a test that calls + ``$platform->modifyLimitQuery()`` and asserts that the resulting SQL looks as expected. + These tests cannot guarantee that the generated SQL is valid syntactically and semantically but they guarantee + that the code works as designed. They provide fast feedback because they don't require a database connection + and can test all platforms in a single test suite run. +2. There is an integration test ``Doctrine\DBAL\Tests\Functional\ModifyLimitQueryTest`` which calls + ``$platform->modifyLimitQuery()`` and executes the generated queries on a real database to which the test suite + is connected. This test guarantees that the generated queries are valid but it's much slower and works + only with one database at a time. + +As you can see, both approaches have their strengths and weaknesses and can complement each other. + +.. warning:: + + Do not mix the unit and the integration approaches in one test. Each of the approaches has its area of application + and purpose. Mixing them makes it harder to identify the reason and the impact of a failing mixed-type test. diff --git a/docs/en/sidebar.rst b/docs/en/sidebar.rst index 404246e3a27..91b6cb2ae85 100644 --- a/docs/en/sidebar.rst +++ b/docs/en/sidebar.rst @@ -18,5 +18,6 @@ reference/caching reference/known-vendor-issues reference/upgrading + reference/testing explanation/implicit-indexes diff --git a/psalm.xml b/psalm.xml index 8cd192d3304..764947f3e44 100644 --- a/psalm.xml +++ b/psalm.xml @@ -84,14 +84,6 @@ - - - - - - + + +