From f03c341c58051e12d540dfc7f1f2734753f3d36e Mon Sep 17 00:00:00 2001 From: Harald Leithner Date: Sun, 30 Mar 2025 16:21:03 +0200 Subject: [PATCH 01/26] [4.x] Remove deprecated and incorrect working quoteNameStr method (#330) * Remove deprecated and incorrect working quoteNameStr method * Add documentation * Update docs/v3-to-v4-update.md Co-authored-by: Richard Fath --------- Co-authored-by: Richard Fath --- docs/index.md | 1 + docs/v3-to-v4-update.md | 19 +++++++++++++++++++ src/DatabaseDriver.php | 30 ------------------------------ 3 files changed, 20 insertions(+), 30 deletions(-) create mode 100644 docs/v3-to-v4-update.md diff --git a/docs/index.md b/docs/index.md index 63cbabffd..c716e3313 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,2 +1,3 @@ * [Overview](overview.md) * [Updating from v1 to v2](v1-to-v2-update.md) +* [Updating from v3 to v4](v3-to-v4-update.md) diff --git a/docs/v3-to-v4-update.md b/docs/v3-to-v4-update.md new file mode 100644 index 000000000..d70d6a00c --- /dev/null +++ b/docs/v3-to-v4-update.md @@ -0,0 +1,19 @@ +## Updating from v3 to v4 + +The following changes were made to the Database package between v3 and v4. + +### Minimum supported PHP version raised + +All Framework packages now require PHP 8.1 or newer. + +### Minimum supported database versions raised + +The following are the minimum supported database versions: + +- MySQL: 5.6 +- PostgreSQL: 9.2.0 +- MS SQL: 11.0.2100.60 (SQL Server 2012) + +### Removed quoteNameStr + +The deprecated method `quoteNameStr` has been removed. Use `quoteNameString` instead. diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php index 25b25677d..433d884f3 100644 --- a/src/DatabaseDriver.php +++ b/src/DatabaseDriver.php @@ -1579,36 +1579,6 @@ protected function quoteNameString($name, $asSinglePart = false) return $q[0] . str_replace('.', "$q[1].$q[0]", $name) . $q[1]; } - /** - * Quote strings coming from quoteName call. - * - * @param array $strArr Array of strings coming from quoteName dot-explosion. - * - * @return string Dot-imploded string of quoted parts. - * - * @since 1.0 - * @deprecated 2.0 Use quoteNameString instead - */ - protected function quoteNameStr($strArr) - { - $parts = []; - $q = $this->nameQuote; - - foreach ($strArr as $part) { - if ($part === null) { - continue; - } - - if (\strlen($q) === 1) { - $parts[] = $q . $part . $q; - } else { - $parts[] = $q[0] . $part . $q[1]; - } - } - - return implode('.', $parts); - } - /** * This function replaces a string identifier with the configured table prefix. * From cf50ddaec67d90cb3faff674aed25b382781d361 Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:27:01 +0200 Subject: [PATCH 02/26] [4.x] add createQuery to DatabaseInterface (#325) * [4.x] add createQuery to DatabaseInterface * extend getQuery($new) to 5.0 * Revert "Revert usage of createQuery till interface has been updated" This reverts commit aca10d7b63ba989c63e2f7b62047d2de46057fca. * update tests * fix test mock * copy doc from class * fix cs * doc --- README.md | 2 +- Tests/Mysql/MysqlExporterTest.php | 2 +- Tests/Mysql/MysqlImporterTest.php | 2 +- Tests/Mysql/MysqlPreparedStatementTest.php | 4 ++-- Tests/Mysqli/MysqliExporterTest.php | 2 +- Tests/Mysqli/MysqliImporterTest.php | 2 +- Tests/Pgsql/PgsqlExporterTest.php | 2 +- Tests/Pgsql/PgsqlImporterTest.php | 2 +- Tests/Pgsql/PgsqlPreparedStatementTest.php | 4 ++-- Tests/Sqlite/SqlitePreparedStatementTest.php | 4 ++-- Tests/Sqlsrv/SqlsrvPreparedStatementTest.php | 4 ++-- docs/v3-to-v4-update.md | 5 +++++ src/DatabaseDriver.php | 6 +++--- src/DatabaseExporter.php | 2 +- src/DatabaseInterface.php | 14 ++++++++++++-- src/Pgsql/PgsqlExporter.php | 2 +- 16 files changed, 37 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 3ebc552d4..84143f08e 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ The `Database\DatabaseIterator` class allows iteration over database results ```php $db = DatabaseDriver::getInstance($options); $iterator = $db->setQuery( - $db->getQuery(true)->select('*')->from('#__content') + $db->createQuery()->select('*')->from('#__content') )->getIterator(); foreach ($iterator as $row) diff --git a/Tests/Mysql/MysqlExporterTest.php b/Tests/Mysql/MysqlExporterTest.php index 01484de0e..f44d075c7 100644 --- a/Tests/Mysql/MysqlExporterTest.php +++ b/Tests/Mysql/MysqlExporterTest.php @@ -43,7 +43,7 @@ protected function setUp(): void ->willReturn('jos_'); $this->db->expects($this->any()) - ->method('getQuery') + ->method('createQuery') ->willReturnCallback(function () { return new MysqlQuery($this->db); }); diff --git a/Tests/Mysql/MysqlImporterTest.php b/Tests/Mysql/MysqlImporterTest.php index 1b6cdf58e..d9484a306 100644 --- a/Tests/Mysql/MysqlImporterTest.php +++ b/Tests/Mysql/MysqlImporterTest.php @@ -69,7 +69,7 @@ protected function setUp(): void ->willReturn('jos_'); $this->db->expects($this->any()) - ->method('getQuery') + ->method('createQuery') ->willReturnCallback(function () { return new MysqlQuery($this->db); }); diff --git a/Tests/Mysql/MysqlPreparedStatementTest.php b/Tests/Mysql/MysqlPreparedStatementTest.php index 6e177f905..f450db223 100644 --- a/Tests/Mysql/MysqlPreparedStatementTest.php +++ b/Tests/Mysql/MysqlPreparedStatementTest.php @@ -72,7 +72,7 @@ protected function tearDown(): void public function testPreparedStatementWithDuplicateKey() { $dummyValue = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ @@ -93,7 +93,7 @@ public function testPreparedStatementWithSingleKey() { $dummyValue = 'test'; $dummyValue2 = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ diff --git a/Tests/Mysqli/MysqliExporterTest.php b/Tests/Mysqli/MysqliExporterTest.php index 4e96342a3..429b41c84 100644 --- a/Tests/Mysqli/MysqliExporterTest.php +++ b/Tests/Mysqli/MysqliExporterTest.php @@ -43,7 +43,7 @@ protected function setUp(): void ->willReturn('jos_'); $this->db->expects($this->any()) - ->method('getQuery') + ->method('createQuery') ->willReturnCallback(function () { return new MysqliQuery($this->db); }); diff --git a/Tests/Mysqli/MysqliImporterTest.php b/Tests/Mysqli/MysqliImporterTest.php index a17638821..b5fb6c1d0 100644 --- a/Tests/Mysqli/MysqliImporterTest.php +++ b/Tests/Mysqli/MysqliImporterTest.php @@ -69,7 +69,7 @@ protected function setUp(): void ->willReturn('jos_'); $this->db->expects($this->any()) - ->method('getQuery') + ->method('createQuery') ->willReturnCallback(function () { return new MysqliQuery($this->db); }); diff --git a/Tests/Pgsql/PgsqlExporterTest.php b/Tests/Pgsql/PgsqlExporterTest.php index 7d92d52c4..ad83cc38b 100644 --- a/Tests/Pgsql/PgsqlExporterTest.php +++ b/Tests/Pgsql/PgsqlExporterTest.php @@ -43,7 +43,7 @@ protected function setUp(): void ->willReturn('jos_'); $this->db->expects($this->any()) - ->method('getQuery') + ->method('createQuery') ->willReturnCallback(function () { return new PgsqlQuery($this->db); }); diff --git a/Tests/Pgsql/PgsqlImporterTest.php b/Tests/Pgsql/PgsqlImporterTest.php index d33a63e1c..92750c88a 100644 --- a/Tests/Pgsql/PgsqlImporterTest.php +++ b/Tests/Pgsql/PgsqlImporterTest.php @@ -69,7 +69,7 @@ protected function setUp(): void ->willReturn('jos_'); $this->db->expects($this->any()) - ->method('getQuery') + ->method('createQuery') ->willReturnCallback(function () { return new PgsqlQuery($this->db); }); diff --git a/Tests/Pgsql/PgsqlPreparedStatementTest.php b/Tests/Pgsql/PgsqlPreparedStatementTest.php index 17dd43e92..41b4fe18d 100644 --- a/Tests/Pgsql/PgsqlPreparedStatementTest.php +++ b/Tests/Pgsql/PgsqlPreparedStatementTest.php @@ -75,7 +75,7 @@ protected function tearDown(): void public function testPreparedStatementWithDuplicateKey() { $dummyValue = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ @@ -96,7 +96,7 @@ public function testPreparedStatementWithSingleKey() { $dummyValue = 'test'; $dummyValue2 = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ diff --git a/Tests/Sqlite/SqlitePreparedStatementTest.php b/Tests/Sqlite/SqlitePreparedStatementTest.php index e3ffd2a82..3ddb258cf 100644 --- a/Tests/Sqlite/SqlitePreparedStatementTest.php +++ b/Tests/Sqlite/SqlitePreparedStatementTest.php @@ -82,7 +82,7 @@ function (string $table): bool { public function testPreparedStatementWithDuplicateKey() { $dummyValue = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ @@ -103,7 +103,7 @@ public function testPreparedStatementWithSingleKey() { $dummyValue = 'test'; $dummyValue2 = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ diff --git a/Tests/Sqlsrv/SqlsrvPreparedStatementTest.php b/Tests/Sqlsrv/SqlsrvPreparedStatementTest.php index 8625fdf77..f21e4b1fc 100644 --- a/Tests/Sqlsrv/SqlsrvPreparedStatementTest.php +++ b/Tests/Sqlsrv/SqlsrvPreparedStatementTest.php @@ -133,7 +133,7 @@ public function testPrepareParameterKeyMappingWithSingleKey() public function testPreparedStatementWithDuplicateKey() { $dummyValue = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ @@ -154,7 +154,7 @@ public function testPreparedStatementWithSingleKey() { $dummyValue = 'test'; $dummyValue2 = 'test'; - $query = static::$connection->getQuery(true); + $query = static::$connection->createQuery(); $query->select('*') ->from($query->quoteName('dbtest')) ->where([ diff --git a/docs/v3-to-v4-update.md b/docs/v3-to-v4-update.md index d70d6a00c..1fecf5c93 100644 --- a/docs/v3-to-v4-update.md +++ b/docs/v3-to-v4-update.md @@ -17,3 +17,8 @@ The following are the minimum supported database versions: ### Removed quoteNameStr The deprecated method `quoteNameStr` has been removed. Use `quoteNameString` instead. + +### DatabaseInterface: `createQuery` method + +`DatabaseInterface` adds a `createQuery` method for creating query objects. Use `createQuery()` instead of `getQuery(true)`. +If you have a custom query class update your adapter's `createQuery()` method to return your custom query class. diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php index 433d884f3..497d9cc0c 100644 --- a/src/DatabaseDriver.php +++ b/src/DatabaseDriver.php @@ -979,7 +979,7 @@ public function getImporter() * Get the current query object or a new DatabaseQuery object. * * @param boolean $new False to return the current query object, True to return a new DatabaseQuery object. - * The $new parameter is deprecated in 2.2 and will be removed in 4.0, use createQuery() instead. + * The $new parameter is deprecated in 2.2 and will be removed in 5.0, use createQuery() instead. * * @return DatabaseQuery * @@ -991,7 +991,7 @@ public function getQuery($new = false) trigger_deprecation( 'joomla/database', '2.2.0', - 'The parameter $new is deprecated and will be removed in 4.0, use %s::createQuery() instead.', + 'The parameter $new is deprecated and will be removed in 5.0, use %s::createQuery() instead.', self::class ); @@ -1718,7 +1718,7 @@ public function setQuery($query, $offset = 0, $limit = 0) if (\is_string($query)) { // Allows taking advantage of bound variables in a direct query: - $query = $this->getQuery(true)->setQuery($query); + $query = $this->createQuery()->setQuery($query); } elseif (!($query instanceof QueryInterface)) { throw new \InvalidArgumentException( sprintf( diff --git a/src/DatabaseExporter.php b/src/DatabaseExporter.php index fd26d110f..dffedf18b 100644 --- a/src/DatabaseExporter.php +++ b/src/DatabaseExporter.php @@ -269,7 +269,7 @@ protected function buildXmlData() } $this->db->setQuery( - $this->db->getQuery(true) + $this->db->createQuery() ->select($this->db->quoteName(array_keys($fields))) ->from($this->db->quoteName($table)) ); diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php index 508b9b954..9b65b24ba 100644 --- a/src/DatabaseInterface.php +++ b/src/DatabaseInterface.php @@ -48,6 +48,15 @@ public function connected(); */ public function createDatabase($options, $utf = true); + /** + * Create a new DatabaseQuery object. + * + * @return QueryInterface + * + * @since 4.0.0 + */ + public function createQuery(): QueryInterface; + /** * Replace special placeholder representing binary field with the original string. * @@ -232,9 +241,10 @@ public function getPrefix(); public function getNumRows(); /** - * Get the current query object or a new QueryInterface object. + * Get the current query object. (Deprecated: Or a new QueryInterface object). * - * @param boolean $new False to return the current query object, True to return a new QueryInterface object. + * @param boolean $new False to return the current query object, True to return a new DatabaseQuery object. + * The $new parameter is deprecated in 2.2 and will be removed in 5.0, use createQuery() instead. * * @return QueryInterface * diff --git a/src/Pgsql/PgsqlExporter.php b/src/Pgsql/PgsqlExporter.php index 08d7d2a3d..82d94e0d0 100644 --- a/src/Pgsql/PgsqlExporter.php +++ b/src/Pgsql/PgsqlExporter.php @@ -126,7 +126,7 @@ protected function buildXmlData() } } - $query = $this->db->getQuery(true); + $query = $this->db->createQuery(); $query->select($query->quoteName(array_keys($fields))) ->from($query->quoteName($table)); $this->db->setQuery($query); From 37cbb884fbea1871f16965c02bff946c9370f43d Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Thu, 3 Jul 2025 11:19:20 +0200 Subject: [PATCH 03/26] Add PHP 8.4 to CI --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a20fe5e7..dc7faa1d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Run Unit tests @@ -84,7 +84,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] db_engine: ['mysql', 'mysqli'] db_version: ['5.7', '8.0'] steps: @@ -110,7 +110,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Run Unit tests @@ -134,7 +134,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] db_version: ['10', '11'] steps: - uses: actions/checkout@v4 @@ -158,7 +158,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Run Unit tests @@ -190,7 +190,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Setup PHP From 6c7deb3bbf2fcad5c574c90c39c3b2f3766e01b9 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Thu, 3 Jul 2025 12:34:04 +0200 Subject: [PATCH 04/26] Update phpstan to v2 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7b8e43fbb..a89c35e33 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ "psr/log": "^1.1", "symfony/phpunit-bridge": "^5.0", "squizlabs/php_codesniffer": "~3.7.2", - "phpstan/phpstan": "1.12.27", - "phpstan/phpstan-deprecation-rules": "1.2.1" + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3" }, "suggest": { "joomla/archive": "To use the ExportCommand class, install joomla/archive", From ff9a35a4c2efd70ade25cd0bd831d9943f632126 Mon Sep 17 00:00:00 2001 From: Brian Teeman Date: Tue, 8 Jul 2025 11:26:32 +0100 Subject: [PATCH 05/26] [4.x] typos (#342) check integration --- README.md | 4 ++-- src/Sqlsrv/SqlsrvQuery.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0d3cfe2d8..ed814252a 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ function search($title) In the first case, the title variable is simply escaped and quoted. Any quote characters in the title string will be prepended with a backslash and the whole string will be wrapped in quotes. -In the second case, the example shows how to treat a search string that will be used in a `LIKE` clause. In this case, the title variable is manually escaped using `escape` with a second argument of `true`. This will force other special characters to be escaped (otherwise you could set youself up for serious performance problems if the user includes too many wildcards). Then, the result is passed to the `quote` method but escaping is turned off (because it has already been done manually). +In the second case, the example shows how to treat a search string that will be used in a `LIKE` clause. In this case, the title variable is manually escaped using `escape` with a second argument of `true`. This will force other special characters to be escaped (otherwise you could set yourself up for serious performance problems if the user includes too many wildcards). Then, the result is passed to the `quote` method but escaping is turned off (because it has already been done manually). In the third case, the title variable is an array so the whole array can be passed to the `quote` method (this saves using a closure and a ) @@ -118,7 +118,7 @@ $count = count($iterator); ``` ## Logging -`Database\DatabaseDriver` implements the `Psr\Log\LoggerAwareInterface` so is ready for intergrating with a logging package that supports that standard. +`Database\DatabaseDriver` implements the `Psr\Log\LoggerAwareInterface` so is ready for integrating with a logging package that supports that standard. Drivers log all errors with a log level of `LogLevel::ERROR`. diff --git a/src/Sqlsrv/SqlsrvQuery.php b/src/Sqlsrv/SqlsrvQuery.php index 66937c41c..76905340d 100644 --- a/src/Sqlsrv/SqlsrvQuery.php +++ b/src/Sqlsrv/SqlsrvQuery.php @@ -712,7 +712,7 @@ protected function fixGroupColumns($selectColumns) $alias = end($table); $table = $table[0]; - // Chek if exists a wildcard with current alias table? + // Check if exists a wildcard with current alias table? if (\in_array($alias, $wildcardTables, true)) { if (!isset($cacheCols[$table])) { $cacheCols[$table] = $this->db->getTableColumns($table); @@ -744,7 +744,7 @@ protected function fixGroupColumns($selectColumns) $table = $matches[1]; $alias = $matches[2] ?? $table; - // Chek if exists a wildcard with current alias table? + // Check if exists a wildcard with current alias table? if (\in_array($alias, $wildcardTables, true)) { if (!isset($cacheCols[$table])) { $cacheCols[$table] = $this->db->getTableColumns($table); From 64af7089d66414e2a3f345b158af7231854cc2a6 Mon Sep 17 00:00:00 2001 From: Robert Deutz Date: Wed, 9 Jul 2025 14:27:32 +0200 Subject: [PATCH 06/26] composer updates --- composer.json | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index a89c35e33..4b91c0211 100644 --- a/composer.json +++ b/composer.json @@ -6,21 +6,21 @@ "homepage": "https://github.com/joomla-framework/database", "license": "GPL-2.0-or-later", "require": { - "php": "^8.1.0", - "joomla/event": "^3.0", + "php": "^8.3.0", + "joomla/event": "dev-4.x-dev", "symfony/deprecation-contracts": "^2|^3" }, "require-dev": { - "joomla/archive": "^3.0", - "joomla/console": "^3.0", - "joomla/di": "^3.0", - "joomla/filesystem": "^3.0", - "joomla/registry": "^3.0", - "joomla/test": "^3.0", - "phpunit/phpunit": "^9.5.28", - "psr/log": "^1.1", - "symfony/phpunit-bridge": "^5.0", - "squizlabs/php_codesniffer": "~3.7.2", + "joomla/archive": "dev-4.x-dev", + "joomla/console": "dev-4.x-dev", + "joomla/di": "dev-4.x-dev", + "joomla/filesystem": "dev-4.x-dev", + "joomla/registry": "dev-4.x-dev", + "joomla/test": "dev-4.x-dev", + "phpunit/phpunit": "^12.0", + "psr/log": "^3.0.2", + "symfony/phpunit-bridge": "^8.0", + "squizlabs/php_codesniffer": "~3.10.2", "phpstan/phpstan": "^2.1.17", "phpstan/phpstan-deprecation-rules": "^2.0.3" }, @@ -49,7 +49,8 @@ "extra": { "branch-alias": { "dev-2.0-dev": "2.0-dev", - "dev-3.x-dev": "3.0-dev" + "dev-3.x-dev": "3.0-dev", + "dev-4.x-dev": "4.0-dev" } } } From 853ffd0f8df3ede4eeb9f9e2f59f1989b078ff8e Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 11 Jul 2025 11:24:27 +0200 Subject: [PATCH 07/26] Raise requirements to PHP 8.3, update dependencies --- composer.json | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index a89c35e33..7c8841b51 100644 --- a/composer.json +++ b/composer.json @@ -6,18 +6,18 @@ "homepage": "https://github.com/joomla-framework/database", "license": "GPL-2.0-or-later", "require": { - "php": "^8.1.0", - "joomla/event": "^3.0", + "php": "^8.3.0", + "joomla/event": "dev-4.x-dev", "symfony/deprecation-contracts": "^2|^3" }, "require-dev": { - "joomla/archive": "^3.0", - "joomla/console": "^3.0", - "joomla/di": "^3.0", - "joomla/filesystem": "^3.0", - "joomla/registry": "^3.0", - "joomla/test": "^3.0", - "phpunit/phpunit": "^9.5.28", + "joomla/archive": "dev-4.x-dev", + "joomla/console": "dev-4.x-dev", + "joomla/di": "dev-4.x-dev", + "joomla/filesystem": "dev-4.x-dev", + "joomla/registry": "dev-4.x-dev", + "joomla/test": "dev-4.x-dev", + "phpunit/phpunit": "^12.2.6", "psr/log": "^1.1", "symfony/phpunit-bridge": "^5.0", "squizlabs/php_codesniffer": "~3.7.2", @@ -49,7 +49,8 @@ "extra": { "branch-alias": { "dev-2.0-dev": "2.0-dev", - "dev-3.x-dev": "3.0-dev" + "dev-3.x-dev": "3.0-dev", + "dev-4.x-dev": "4.0-dev" } } } From 569819448c3645309df63c7fe551d478937a877c Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 11 Jul 2025 11:30:58 +0200 Subject: [PATCH 08/26] Update documentation --- README.md | 8 ++++---- SECURITY.md | 3 ++- docs/index.md | 1 + docs/v2-to-v3-update.md | 12 ++++++++++++ docs/v3-to-v4-update.md | 10 +++++++++- 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 docs/v2-to-v3-update.md diff --git a/README.md b/README.md index 0d3cfe2d8..2aa1f7ee8 100644 --- a/README.md +++ b/README.md @@ -173,12 +173,12 @@ This is the log file: ## Installation via Composer -Add `"joomla/database": "~3.0"` to the require block in your composer.json and then run `composer install`. +Add `"joomla/database": "~4.0"` to the require block in your composer.json and then run `composer install`. ```json { "require": { - "joomla/database": "~3.0" + "joomla/database": "~4.0" } } ``` @@ -186,11 +186,11 @@ Add `"joomla/database": "~3.0"` to the require block in your composer.json and t Alternatively, you can simply run the following from the command line: ```sh -composer require joomla/database "~3.0" +composer require joomla/database "~4.0" ``` If you want to include the test sources, use ```sh -composer require --prefer-source joomla/database "~3.0" +composer require --prefer-source joomla/database "~4.0" ``` diff --git a/SECURITY.md b/SECURITY.md index ed9f30a8f..0ea4e4f4e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,7 +5,8 @@ These versions are currently being supported with security updates: | Version | Supported | -| ------- | ------------------ | +|---------| ------------------ | +| 4.x.x | :white_check_mark: | | 3.x.x | :white_check_mark: | | 2.0.x | :white_check_mark: | | 1.8.x | :x: | diff --git a/docs/index.md b/docs/index.md index c716e3313..656c20a57 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,4 @@ * [Overview](overview.md) * [Updating from v1 to v2](v1-to-v2-update.md) +* [Updating from v2 to v3](v2-to-v3-update.md) * [Updating from v3 to v4](v3-to-v4-update.md) diff --git a/docs/v2-to-v3-update.md b/docs/v2-to-v3-update.md new file mode 100644 index 000000000..214dfe092 --- /dev/null +++ b/docs/v2-to-v3-update.md @@ -0,0 +1,12 @@ +## Updating from v2 to v3 + +The following changes were made to the Database package between v2 and v3. + +### Minimum supported PHP version raised + +All Framework packages now require PHP 8.1 or newer. + +### `DatabaseDriver::getQuery(true)` has been deprecated + +`DatabaseDriver::getQuery()` with the parameter set to `true` returns a new `DatabaseQuery` object, while the unset parameter or set to `false` returns the last query set. +This parameter has been deprecated and will be removed in 5.0. `DatabaseDriver::getQuery()` will only return the last set query in the future and instead you should use `DatabaseDriver::createQuery()`. diff --git a/docs/v3-to-v4-update.md b/docs/v3-to-v4-update.md index 1fecf5c93..d8bcb537d 100644 --- a/docs/v3-to-v4-update.md +++ b/docs/v3-to-v4-update.md @@ -4,7 +4,7 @@ The following changes were made to the Database package between v3 and v4. ### Minimum supported PHP version raised -All Framework packages now require PHP 8.1 or newer. +All Framework packages now require PHP 8.3 or newer. ### Minimum supported database versions raised @@ -22,3 +22,11 @@ The deprecated method `quoteNameStr` has been removed. Use `quoteNameString` ins `DatabaseInterface` adds a `createQuery` method for creating query objects. Use `createQuery()` instead of `getQuery(true)`. If you have a custom query class update your adapter's `createQuery()` method to return your custom query class. + +### Removed deprecated `LimitableInterface` and `PreparableInterface` + +The interfaces `Joomla\Database\Query\LimitableInterface` and `Joomla\Database\Query\PreparableInterface` have been removed and its signatures added to the `QueryInterface`. All objects implementing one of these interfaces need to implement the `QueryInterface` instead. + +### Removed deprecated `DatabaseQuery::castAsChar()` + +The deprecated method `DatabaseQuery::castAsChar()` as been removed. Use `$query->castAs('CHAR', $value)` instead. From 8a1de00c639d9feecef21ca9755c2a52ad42ef46 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 11 Jul 2025 11:31:26 +0200 Subject: [PATCH 09/26] Remove deprecated interfaces and update QueryInterface --- src/Query/LimitableInterface.php | 61 ------------ src/Query/PreparableInterface.php | 75 --------------- src/QueryInterface.php | 150 +++++++++++++++++++++++++++++- 3 files changed, 147 insertions(+), 139 deletions(-) delete mode 100644 src/Query/LimitableInterface.php delete mode 100644 src/Query/PreparableInterface.php diff --git a/src/Query/LimitableInterface.php b/src/Query/LimitableInterface.php deleted file mode 100644 index 2932b31ea..000000000 --- a/src/Query/LimitableInterface.php +++ /dev/null @@ -1,61 +0,0 @@ -setLimit(100, 0); (retrieve 100 rows, starting at first record) - * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) - * - * @param integer $limit The limit for the result set - * @param integer $offset The offset for the result set - * - * @return $this - * - * @since 1.0 - */ - public function setLimit($limit = 0, $offset = 0); -} diff --git a/src/Query/PreparableInterface.php b/src/Query/PreparableInterface.php deleted file mode 100644 index 125d4325c..000000000 --- a/src/Query/PreparableInterface.php +++ /dev/null @@ -1,75 +0,0 @@ -innerJoin('b', 'b.id = a.id')->innerJoin('c', 'c.id = b.id'); + * + * @param string $table The name of table. + * @param string $condition The join condition. + * + * @return $this + * + * @since 4.0 + */ + public function innerJoin($table, $condition = null); + + /** + * Add an OUTER JOIN clause to the query. + * + * Usage: + * $query->outerJoin('b', 'b.id = a.id')->leftJoin('c', 'c.id = b.id'); + * + * @param string $table The name of table. + * @param string $condition The join condition. + * + * @return $this + * + * @since 4.0 + */ + public function outerJoin($table, $condition = null); + + /** + * Add a LEFT JOIN clause to the query. + * + * Usage: + * $query->leftJoin('b', 'b.id = a.id')->leftJoin('c', 'c.id = b.id'); + * + * @param string $table The name of table. + * @param string $condition The join condition. + * + * @return $this + * + * @since 4.0 + */ + public function leftJoin($table, $condition = null); + + /** + * Add a RIGHT JOIN clause to the query. + * + * Usage: + * $query->rightJoin('b', 'b.id = a.id')->rightJoin('c', 'c.id = b.id'); + * + * @param string $table The name of table. + * @param string $condition The join condition. + * + * @return $this + * + * @since 4.0 + */ + public function rightJoin($table, $condition = null); + /** * Get the length of a string in bytes. * @@ -537,6 +595,21 @@ public function selectRowNumber($orderBy, $orderColumnAlias); */ public function set($conditions, $glue = ','); + /** + * Allows a direct query to be provided to the database driver's setQuery() method, but still allow queries + * to have bounded variables. + * + * Usage: + * $query->setQuery('select * from #__users'); + * + * @param DatabaseQuery|string $sql A SQL query string or DatabaseQuery object + * + * @return $this + * + * @since 4.0 + */ + public function setQuery($sql); + /** * Add a table name to the UPDATE clause of the query. * @@ -722,4 +795,75 @@ public function querySet($query); * @since 2.0.0 */ public function toQuerySet(); + + /** + * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. + * + * @param array|string|integer $key The key that will be used in your SQL query to reference the value. Usually of + * the form ':key', but can also be an integer. + * @param mixed $value The value that will be bound. It can be an array, in this case it has to be + * same length of $key; The value is passed by reference to support output + * parameters such as those possible with stored procedures. + * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it + * has to be same length of $key + * @param integer $length The length of the variable. Usually required for OUTPUT parameters. + * @param array $driverOptions Optional driver options to be used. + * + * @return $this + * + * @since 4.0 + */ + public function bind($key, &$value, $dataType = ParameterType::STRING, $length = 0, $driverOptions = []); + + /** + * Method to unbind a bound variable. + * + * @param array|string|integer $key The key or array of keys to unbind. + * + * @return $this + * + * @since 4.0.0 + */ + public function unbind($key); + + /** + * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is returned. + * + * @param mixed $key The bounded variable key to retrieve. + * + * @return mixed + * + * @since 4.0 + */ + public function &getBounded($key = null); + + /** + * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of + * results, or start at a particular offset. + * + * @param string $query The query in string format + * @param integer $limit The limit for the result set + * @param integer $offset The offset for the result set + * + * @return string + * + * @since 4.0 + */ + public function processLimit($query, $limit, $offset = 0); + + /** + * Sets the offset and limit for the result set, if the database driver supports it. + * + * Usage: + * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) + * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) + * + * @param integer $limit The limit for the result set + * @param integer $offset The offset for the result set + * + * @return $this + * + * @since 4.0 + */ + public function setLimit($limit = 0, $offset = 0); } From b855c6f5598e0f42dcdd8f87db3ad34866db43ee Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 11 Jul 2025 11:32:22 +0200 Subject: [PATCH 10/26] Remove deprecated DatabaseQuery::castAsChar() --- src/DatabaseQuery.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/DatabaseQuery.php b/src/DatabaseQuery.php index b288c4df9..e7866ab52 100644 --- a/src/DatabaseQuery.php +++ b/src/DatabaseQuery.php @@ -558,26 +558,6 @@ public function castAs(string $type, string $value, ?string $length = null) } } - /** - * Casts a value to a char. - * - * Ensure that the value is properly quoted before passing to the method. - * - * Usage: - * $query->select($query->castAsChar('a')); - * - * @param string $value The value to cast as a char. - * - * @return string SQL statement to cast the value as a char type. - * - * @since 1.0 - * @deprecated 3.0 Use $query->castAs('CHAR', $value) - */ - public function castAsChar($value) - { - return $this->castAs('CHAR', $value); - } - /** * Gets the number of characters in a string. * From d20f06d7b5d8e0bc9c72047229c37427be904776 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 11 Jul 2025 11:32:44 +0200 Subject: [PATCH 11/26] Update unittests to phpunit 12 --- Tests/DatabaseExporterTest.php | 100 +++++++------- Tests/DatabaseFactoryTest.php | 188 ++++++++++++++++----------- Tests/DatabaseImporterTest.php | 49 ++++--- Tests/DatabaseQueryTest.php | 136 +++++++++---------- Tests/Pgsql/PgsqlQueryTest.php | 8 -- Tests/Query/QueryElementTest.php | 182 +++++++++++++------------- Tests/Sqlsrv/SqlsrvQueryTest.php | 8 -- Tests/Stubs/TestDatabaseExporter.php | 28 ++++ Tests/Stubs/TestDatabaseImporter.php | 32 +++++ Tests/Stubs/TestDatabaseQuery.php | 22 ++++ 10 files changed, 428 insertions(+), 325 deletions(-) create mode 100644 Tests/Stubs/TestDatabaseExporter.php create mode 100644 Tests/Stubs/TestDatabaseImporter.php create mode 100644 Tests/Stubs/TestDatabaseQuery.php diff --git a/Tests/DatabaseExporterTest.php b/Tests/DatabaseExporterTest.php index 3d11e5e31..e2bb22a9f 100644 --- a/Tests/DatabaseExporterTest.php +++ b/Tests/DatabaseExporterTest.php @@ -8,8 +8,12 @@ namespace Joomla\Database\Tests; use Joomla\Database\DatabaseExporter; +use Joomla\Database\DatabaseImporter; use Joomla\Database\DatabaseInterface; +use Joomla\Database\Tests\Stubs\TestDatabaseExporter; +use Joomla\Database\Tests\Stubs\TestDatabaseImporter; use Joomla\Test\TestHelper; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -18,21 +22,39 @@ */ class DatabaseExporterTest extends TestCase { + /** + * Importer object + * + * @var DatabaseExporter + */ + private $exporter; + + /** + * Sets up the fixture. + * + * This method is called before a test is executed. + * + * @return void + */ + protected function setUp(): void + { + parent::setUp(); + + $this->exporter = new TestDatabaseExporter(); + } + /** * @testdox The exporter is correctly configured when instantiated */ public function testInstantiation() { - /** @var DatabaseExporter|MockObject $exporter */ - $exporter = $this->getMockForAbstractClass(DatabaseExporter::class); - $expected = (object) [ 'withStructure' => true, 'withData' => false, ]; - $this->assertEquals($expected, TestHelper::getValue($exporter, 'options')); - $this->assertSame('xml', TestHelper::getValue($exporter, 'asFormat')); + $this->assertEquals($expected, TestHelper::getValue($this->exporter, 'options')); + $this->assertSame('xml', TestHelper::getValue($this->exporter, 'asFormat')); } /** @@ -40,34 +62,33 @@ public function testInstantiation() */ public function testAsXml() { - /** @var DatabaseExporter|MockObject $exporter */ - $exporter = $this->getMockForAbstractClass(DatabaseExporter::class); - - $this->assertSame($exporter, $exporter->asXml(), 'The exporter supports method chaining'); + $this->assertSame($this->exporter, $this->exporter->asXml(), 'The exporter supports method chaining'); - $this->assertSame('xml', TestHelper::getValue($exporter, 'asFormat')); + $this->assertSame('xml', TestHelper::getValue($this->exporter, 'asFormat')); } /** * Data provider for from test cases * - * @return \Generator + * @return array */ - public function dataFrom(): \Generator + public static function dataFrom(): array { - yield 'single table' => [ - '#__dbtest', - false, - ]; - - yield 'multiple tables' => [ - ['#__content', '#__dbtest'], - false, - ]; - - yield 'incorrect table data type' => [ - new \stdClass(), - true, + return [ + 'single table' => [ + '#__dbtest', + false, + ], + + 'multiple tables' => [ + ['#__content', '#__dbtest'], + false, + ], + + 'incorrect table data type' => [ + new \stdClass(), + true, + ], ]; } @@ -76,21 +97,17 @@ public function dataFrom(): \Generator * * @param string[]|string $from The name of a single table, or an array of the table names to export. * @param boolean $shouldRaiseException Flag indicating the exporter should raise an exception for an unsupported data type - * - * @dataProvider dataFrom */ + #[DataProvider('dataFrom')] public function testFrom($from, bool $shouldRaiseException) { if ($shouldRaiseException) { $this->expectException(\InvalidArgumentException::class); } - /** @var DatabaseExporter|MockObject $exporter */ - $exporter = $this->getMockForAbstractClass(DatabaseExporter::class); - - $this->assertSame($exporter, $exporter->from($from), 'The exporter supports method chaining'); + $this->assertSame($this->exporter, $this->exporter->from($from), 'The exporter supports method chaining'); - $this->assertSame((array) $from, TestHelper::getValue($exporter, 'from')); + $this->assertSame((array) $from, TestHelper::getValue($this->exporter, 'from')); } /** @@ -98,13 +115,10 @@ public function testFrom($from, bool $shouldRaiseException) */ public function testSetDbo() { - /** @var DatabaseExporter|MockObject $exporter */ - $exporter = $this->getMockForAbstractClass(DatabaseExporter::class); - /** @var DatabaseInterface|MockObject $db */ $db = $this->createMock(DatabaseInterface::class); - $this->assertSame($exporter, $exporter->setDbo($db), 'The exporter supports method chaining'); + $this->assertSame($this->exporter, $this->exporter->setDbo($db), 'The exporter supports method chaining'); } /** @@ -112,12 +126,9 @@ public function testSetDbo() */ public function testWithStructure() { - /** @var DatabaseExporter|MockObject $exporter */ - $exporter = $this->getMockForAbstractClass(DatabaseExporter::class); + $this->assertSame($this->exporter, $this->exporter->withStructure(false), 'The exporter supports method chaining'); - $this->assertSame($exporter, $exporter->withStructure(false), 'The exporter supports method chaining'); - - $options = TestHelper::getValue($exporter, 'options'); + $options = TestHelper::getValue($this->exporter, 'options'); $this->assertFalse($options->withStructure); } @@ -127,12 +138,9 @@ public function testWithStructure() */ public function testWithData() { - /** @var DatabaseExporter|MockObject $exporter */ - $exporter = $this->getMockForAbstractClass(DatabaseExporter::class); - - $this->assertSame($exporter, $exporter->withData(true), 'The exporter supports method chaining'); + $this->assertSame($this->exporter, $this->exporter->withData(true), 'The exporter supports method chaining'); - $options = TestHelper::getValue($exporter, 'options'); + $options = TestHelper::getValue($this->exporter, 'options'); $this->assertTrue($options->withData); } diff --git a/Tests/DatabaseFactoryTest.php b/Tests/DatabaseFactoryTest.php index 17433bef1..4fbfd7148 100644 --- a/Tests/DatabaseFactoryTest.php +++ b/Tests/DatabaseFactoryTest.php @@ -18,6 +18,7 @@ use Joomla\Database\QueryInterface; use Joomla\Database\StatementInterface; use Joomla\Test\TestHelper; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** @@ -49,18 +50,20 @@ protected function setUp(): void /** * Data provider for driver test cases * - * @return \Generator + * @return array */ - public function dataGetDriver(): \Generator + public static function dataGetDriver(): array { - yield 'supported driver' => [ - 'mysqli', - false, - ]; + return [ + 'supported driver' => [ + 'mysqli', + false, + ], - yield 'unsupported exporter' => [ - 'mariadb', - true, + 'unsupported exporter' => [ + 'mariadb', + true, + ], ]; } @@ -69,9 +72,8 @@ public function dataGetDriver(): \Generator * * @param string $adapter The type of adapter to create * @param boolean $shouldRaiseException Flag indicating the factory should raise an exception for an unsupported adapter - * - * @dataProvider dataGetDriver */ + #[DataProvider('dataGetDriver')] public function testGetDriver(string $adapter, bool $shouldRaiseException) { if ($shouldRaiseException) { @@ -87,26 +89,28 @@ public function testGetDriver(string $adapter, bool $shouldRaiseException) /** * Data provider for exporter test cases * - * @return \Generator + * @return array */ - public function dataGetExporter(): \Generator + public static function dataGetExporter(): array { - yield 'exporter without database driver' => [ - 'mysqli', - false, - null, - ]; + return [ + 'exporter without database driver' => [ + 'mysqli', + false, + false, + ], - yield 'exporter with database driver' => [ - 'mysqli', - false, - $this->createMock(MysqliDriver::class), - ]; + 'exporter with database driver' => [ + 'mysqli', + false, + true, + ], - yield 'unsupported exporter' => [ - 'mariadb', - true, - null, + 'unsupported exporter' => [ + 'mariadb', + true, + false, + ], ]; } @@ -116,15 +120,20 @@ public function dataGetExporter(): \Generator * @param string $adapter The type of adapter to create * @param boolean $shouldRaiseException Flag indicating the factory should raise an exception for an unsupported adapter * @param DatabaseDriver|null $databaseDriver The optional database driver to be injected into the exporter - * - * @dataProvider dataGetExporter */ - public function testGetExporter(string $adapter, bool $shouldRaiseException, ?DatabaseDriver $databaseDriver) + #[DataProvider('dataGetExporter')] + public function testGetExporter(string $adapter, bool $shouldRaiseException, bool $createDb) { if ($shouldRaiseException) { $this->expectException(UnsupportedAdapterException::class); } + $databaseDriver = null; + + if ($createDb) { + $databaseDriver = $this->createMock(MysqliDriver::class); + } + $exporter = $this->factory->getExporter($adapter, $databaseDriver); $this->assertInstanceOf( @@ -143,44 +152,51 @@ public function testGetExporter(string $adapter, bool $shouldRaiseException, ?Da /** * Data provider for importer test cases * - * @return \Generator + * @return array */ - public function dataGetImporter(): \Generator + public static function dataGetImporter(): array { - yield 'importer without database driver' => [ - 'mysqli', - false, - null, - ]; + return [ + 'importer without database driver' => [ + 'mysqli', + false, + false, + ], - yield 'importer with database driver' => [ - 'mysqli', - false, - $this->createMock(MysqliDriver::class), - ]; + 'importer with database driver' => [ + 'mysqli', + false, + true, + ], - yield 'unsupported importer' => [ - 'mariadb', - true, - null, + 'unsupported importer' => [ + 'mariadb', + true, + false, + ], ]; } /** * @testdox The factory builds a database importer correctly * - * @param string $adapter The type of adapter to create - * @param boolean $shouldRaiseException Flag indicating the factory should raise an exception for an unsupported adapter - * @param DatabaseDriver|null $databaseDriver The optional database driver to be injected into the importer - * - * @dataProvider dataGetImporter + * @param string $adapter The type of adapter to create + * @param boolean $shouldRaiseException Flag indicating the factory should raise an exception for an unsupported adapter + * @param boolean $createDb The optional database driver to be injected into the importer */ - public function testGetImporter(string $adapter, bool $shouldRaiseException, ?DatabaseDriver $databaseDriver) + #[DataProvider('dataGetImporter')] + public function testGetImporter(string $adapter, bool $shouldRaiseException, bool $createDb) { if ($shouldRaiseException) { $this->expectException(UnsupportedAdapterException::class); } + $databaseDriver = null; + + if ($createDb) { + $databaseDriver = $this->createMock(MysqliDriver::class); + } + $importer = $this->factory->getImporter($adapter, $databaseDriver); $this->assertInstanceOf( @@ -199,26 +215,33 @@ public function testGetImporter(string $adapter, bool $shouldRaiseException, ?Da /** * Data provider for iterator test cases * - * @return \Generator + * @return array */ - public function dataGetIterator(): \Generator + public static function dataGetIterator(): array { - yield 'driver without custom iterator' => [ - 'mysqli', - $this->createMock(StatementInterface::class), + return [ + 'driver without custom iterator' => [ + 'mysqli', + true, + ], ]; } /** * @testdox The factory builds a database iterator correctly * - * @param string $adapter The type of adapter to create - * @param StatementInterface $statement Statement holding the result set to be iterated. - * - * @dataProvider dataGetIterator + * @param string $adapter The type of adapter to create + * @param bool $createStatement Statement holding the result set to be iterated. */ - public function testGetIterator(string $adapter, StatementInterface $statement) + #[DataProvider('dataGetIterator')] + public function testGetIterator(string $adapter, bool $createStatement) { + $statement = null; + + if ($createStatement) { + $statement = $this->createMock(StatementInterface::class); + } + $this->assertInstanceOf( DatabaseIterator::class, $this->factory->getIterator($adapter, $statement) @@ -228,38 +251,45 @@ public function testGetIterator(string $adapter, StatementInterface $statement) /** * Data provider for query test cases * - * @return \Generator + * @return array */ - public function dataGetQuery(): \Generator + public static function dataGetQuery(): array { - yield 'supported query' => [ - 'mysqli', - false, - $this->createMock(MysqliDriver::class), - ]; + return [ + 'supported query' => [ + 'mysqli', + false, + true, + ], - yield 'unsupported query' => [ - 'mariadb', - true, - null, + 'unsupported query' => [ + 'mariadb', + true, + false, + ], ]; } /** * @testdox The factory builds a database query object correctly * - * @param string $adapter The type of adapter to create - * @param boolean $shouldRaiseException Flag indicating the factory should raise an exception for an unsupported adapter - * @param DatabaseDriver|null $databaseDriver The optional database driver to be injected into the importer - * - * @dataProvider dataGetQuery + * @param string $adapter The type of adapter to create + * @param boolean $shouldRaiseException Flag indicating the factory should raise an exception for an unsupported adapter + * @param boolean $createDb The optional database driver to be injected into the importer */ - public function testGetQuery(string $adapter, bool $shouldRaiseException, ?DatabaseDriver $databaseDriver) + #[DataProvider('dataGetQuery')] + public function testGetQuery(string $adapter, bool $shouldRaiseException, bool $createDb) { if ($shouldRaiseException) { $this->expectException(UnsupportedAdapterException::class); } + $databaseDriver = null; + + if ($createDb) { + $databaseDriver = $this->createMock(MysqliDriver::class); + } + $this->assertInstanceOf( QueryInterface::class, $this->factory->getQuery($adapter, $databaseDriver) diff --git a/Tests/DatabaseImporterTest.php b/Tests/DatabaseImporterTest.php index 8a6a4ef4d..c3e369163 100644 --- a/Tests/DatabaseImporterTest.php +++ b/Tests/DatabaseImporterTest.php @@ -9,6 +9,8 @@ use Joomla\Database\DatabaseImporter; use Joomla\Database\DatabaseInterface; +use Joomla\Database\Tests\Stubs\TestDatabaseImporter; +use Joomla\Database\Tests\Stubs\TestDatabaseQuery; use Joomla\Test\TestHelper; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -18,20 +20,38 @@ */ class DatabaseImporterTest extends TestCase { + /** + * Importer object + * + * @var DatabaseImporter + */ + private $importer; + + /** + * Sets up the fixture. + * + * This method is called before a test is executed. + * + * @return void + */ + protected function setUp(): void + { + parent::setUp(); + + $this->importer = new TestDatabaseImporter(); + } + /** * @testdox The importer is correctly configured when instantiated */ public function testInstantiation() { - /** @var DatabaseImporter|MockObject $importer */ - $importer = $this->getMockForAbstractClass(DatabaseImporter::class); - $expected = (object) [ 'withStructure' => true, ]; - $this->assertEquals($expected, TestHelper::getValue($importer, 'options')); - $this->assertSame('xml', TestHelper::getValue($importer, 'asFormat')); + $this->assertEquals($expected, TestHelper::getValue($this->importer, 'options')); + $this->assertSame('xml', TestHelper::getValue($this->importer, 'asFormat')); } /** @@ -39,12 +59,9 @@ public function testInstantiation() */ public function testAsXml() { - /** @var DatabaseImporter|MockObject $importer */ - $importer = $this->getMockForAbstractClass(DatabaseImporter::class); + $this->assertSame($this->importer, $this->importer->asXml(), 'The importer supports method chaining'); - $this->assertSame($importer, $importer->asXml(), 'The importer supports method chaining'); - - $this->assertSame('xml', TestHelper::getValue($importer, 'asFormat')); + $this->assertSame('xml', TestHelper::getValue($this->importer, 'asFormat')); } /** @@ -52,13 +69,10 @@ public function testAsXml() */ public function testSetDbo() { - /** @var DatabaseImporter|MockObject $importer */ - $importer = $this->getMockForAbstractClass(DatabaseImporter::class); - /** @var DatabaseInterface|MockObject $db */ $db = $this->createMock(DatabaseInterface::class); - $this->assertSame($importer, $importer->setDbo($db), 'The importer supports method chaining'); + $this->assertSame($this->importer, $this->importer->setDbo($db), 'The importer supports method chaining'); } /** @@ -66,12 +80,9 @@ public function testSetDbo() */ public function testWithStructure() { - /** @var DatabaseImporter|MockObject $importer */ - $importer = $this->getMockForAbstractClass(DatabaseImporter::class); - - $this->assertSame($importer, $importer->withStructure(false), 'The importer supports method chaining'); + $this->assertSame($this->importer, $this->importer->withStructure(false), 'The importer supports method chaining'); - $options = TestHelper::getValue($importer, 'options'); + $options = TestHelper::getValue($this->importer, 'options'); $this->assertFalse($options->withStructure); } diff --git a/Tests/DatabaseQueryTest.php b/Tests/DatabaseQueryTest.php index f225e1ad3..e29402af2 100644 --- a/Tests/DatabaseQueryTest.php +++ b/Tests/DatabaseQueryTest.php @@ -11,6 +11,8 @@ use Joomla\Database\Exception\QueryTypeAlreadyDefinedException; use Joomla\Database\Exception\UnknownTypeException; use Joomla\Database\ParameterType; +use Joomla\Database\Tests\Stubs\TestDatabaseQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -45,10 +47,7 @@ protected function setUp(): void parent::setUp(); $this->db = $this->createMock(DatabaseInterface::class); - $this->query = $this->getMockForAbstractClass( - DatabaseQuery::class, - [$this->db] - ); + $this->query = new TestDatabaseQuery($this->db); } /** @@ -77,14 +76,6 @@ public function testCallChangeQueryType() ->call('foo'); } - /** - * @testdox A string is cast as a character string for the driver - */ - public function testCastAsChar() - { - $this->assertSame('foo', $this->query->castAsChar('foo')); - } - /** * @testdox A string is cast as a character string for the driver */ @@ -113,12 +104,14 @@ public function testCastAsWithUnknownType() /** * Data provider for character length test cases * - * @return \Generator + * @return array */ - public function dataCharLength(): \Generator + public static function dataCharLength(): array { - yield 'field without comparison' => ['a.title', null, null, 'CHAR_LENGTH(a.title)']; - yield 'field with comparison' => ['a.title', '!=', '0', 'CHAR_LENGTH(a.title) != 0']; + return [ + 'field without comparison' => ['a.title', null, null, 'CHAR_LENGTH(a.title)'], + 'field with comparison' => ['a.title', '!=', '0', 'CHAR_LENGTH(a.title) != 0'], + ]; } /** @@ -128,9 +121,8 @@ public function dataCharLength(): \Generator * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * @param string $expected The expected query string. - * - * @dataProvider dataCharLength */ + #[DataProvider('dataCharLength')] public function testCharLength(string $field, ?string $operator, ?string $condition, string $expected) { $this->assertSame( @@ -156,12 +148,14 @@ public function testColumns() /** * Data provider for concatenate test cases * - * @return \Generator + * @return array */ - public function dataConcatenate(): \Generator + public static function dataConcatenate(): array { - yield 'values without separator' => [['foo', 'bar'], null, 'CONCATENATE(foo || bar)']; - yield 'values with separator' => [['foo', 'bar'], ' and ', "CONCATENATE(foo || ' and ' || bar)"]; + return [ + 'values without separator' => [['foo', 'bar'], null, 'CONCATENATE(foo || bar)'], + 'values with separator' => [['foo', 'bar'], ' and ', "CONCATENATE(foo || ' and ' || bar)"], + ]; } /** @@ -170,9 +164,8 @@ public function dataConcatenate(): \Generator * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * @param string $expected The expected query string. - * - * @dataProvider dataConcatenate */ + #[DataProvider('dataConcatenate')] public function testConcatenate(array $values, ?string $separator, string $expected) { $this->db->expects($this->any()) @@ -201,12 +194,14 @@ public function testCurrentTimestamp() /** * Data provider for dateAdd test cases * - * @return \Generator + * @return array */ - public function dataDateAdd(): \Generator + public static function dataDateAdd(): array { - yield 'date with positive interval' => ["'2019-10-13'", '1', 'DAY', "DATE_ADD('2019-10-13', INTERVAL 1 DAY)"]; - yield 'date with negative interval' => ["'2019-10-13'", '-1', 'DAY', "DATE_ADD('2019-10-13', INTERVAL -1 DAY)"]; + return [ + 'date with positive interval' => ["'2019-10-13'", '1', 'DAY', "DATE_ADD('2019-10-13', INTERVAL 1 DAY)"], + 'date with negative interval' => ["'2019-10-13'", '-1', 'DAY', "DATE_ADD('2019-10-13', INTERVAL -1 DAY)"], + ]; } /** @@ -216,9 +211,8 @@ public function dataDateAdd(): \Generator * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * @param string $expected The expected query string. - * - * @dataProvider dataDateAdd */ + #[DataProvider('dataDateAdd')] public function testDateAdd(string $date, string $interval, string $datePart, string $expected) { $this->assertSame( @@ -525,12 +519,14 @@ public function testLength() /** * Data provider for null date test cases * - * @return \Generator + * @return array */ - public function dataNullDate(): \Generator + public static function dataNullDate(): array { - yield 'null date with quote' => [true, "'0000-00-00 00:00:00'"]; - yield 'null date without quote' => [false, '0000-00-00 00:00:00']; + return [ + 'null date with quote' => [true, "'0000-00-00 00:00:00'"], + 'null date without quote' => [false, '0000-00-00 00:00:00'], + ]; } /** @@ -538,9 +534,8 @@ public function dataNullDate(): \Generator * * @param boolean $quoted Optionally wraps the null date in database quotes (true by default). * @param string $expected The expected query string. - * - * @dataProvider dataNullDate */ + #[DataProvider('dataNullDate')] public function testNullDate(bool $quoted, string $expected) { $this->db->expects($this->once()) @@ -566,10 +561,7 @@ public function testNullDateException() { $this->expectException(\RuntimeException::class); - $query = $this->getMockForAbstractClass( - DatabaseQuery::class, - [] - ); + $query = new TestDatabaseQuery(); $query->nullDate(); } @@ -627,10 +619,7 @@ public function testIsNullDatetimeException() { $this->expectException(\RuntimeException::class); - $query = $this->getMockForAbstractClass( - DatabaseQuery::class, - [] - ); + $query = new TestDatabaseQuery(); $query->isNullDatetime('a.created'); } @@ -673,10 +662,7 @@ public function testQuoteException() { $this->expectException(\RuntimeException::class); - $query = $this->getMockForAbstractClass( - DatabaseQuery::class, - [] - ); + $query = new TestDatabaseQuery(); $query->quote('foo'); } @@ -705,10 +691,7 @@ public function testQuoteNameException() { $this->expectException(\RuntimeException::class); - $query = $this->getMockForAbstractClass( - DatabaseQuery::class, - [] - ); + $query = new TestDatabaseQuery(); $query->quoteName('foo'); } @@ -930,28 +913,30 @@ public function testAndWhere() /** * Data provider for bind test cases * - * @return \Generator - */ - public function dataBind(): \Generator - { - yield 'string field' => ['foo', 'bar', ParameterType::STRING, [ - 'foo' => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], - ]]; - yield 'numeric field' => ['foo', 42, ParameterType::INTEGER, [ - 'foo' => (object) ['value' => 42, 'dataType' => 'int', 'length' => 0, 'driverOptions' => []], - ]]; - yield 'numeric key' => [1, 'bar', ParameterType::STRING, [ - 1 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], - ]]; - yield 'array of data' => [[1, 'foo'], [42, 'bar'], [ParameterType::INTEGER, ParameterType::STRING], [ - 1 => (object) ['value' => 42, 'dataType' => 'int', 'length' => 0, 'driverOptions' => []], - 'foo' => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], - ]]; - yield 'key array, single data value' => [[1, 2, 3], 'bar', ParameterType::STRING, [ - 1 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], - 2 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], - 3 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], - ]]; + * @return array + */ + public static function dataBind(): array + { + return [ + 'string field' => ['foo', 'bar', ParameterType::STRING, [ + 'foo' => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], + ]], + 'numeric field' => ['foo', 42, ParameterType::INTEGER, [ + 'foo' => (object) ['value' => 42, 'dataType' => 'int', 'length' => 0, 'driverOptions' => []], + ]], + 'numeric key' => [1, 'bar', ParameterType::STRING, [ + 1 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], + ]], + 'array of data' => [[1, 'foo'], [42, 'bar'], [ParameterType::INTEGER, ParameterType::STRING], [ + 1 => (object) ['value' => 42, 'dataType' => 'int', 'length' => 0, 'driverOptions' => []], + 'foo' => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], + ]], + 'key array, single data value' => [[1, 2, 3], 'bar', ParameterType::STRING, [ + 1 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], + 2 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], + 3 => (object) ['value' => 'bar', 'dataType' => 'string', 'length' => 0, 'driverOptions' => []], + ]], + ]; } /** @@ -965,9 +950,8 @@ public function dataBind(): \Generator * @param array|string $dataType Constant corresponding to a SQL datatype. It can be an array, in this case it * has to be same length of $key * @param array $expected The expected structure of `$bounded` - * - * @dataProvider dataBind */ + #[DataProvider('dataBind')] public function testBind($key, $value, $dataType, $expected) { $this->assertSame($this->query, $this->query->bind($key, $value, $dataType), 'The query builder supports method chaining'); diff --git a/Tests/Pgsql/PgsqlQueryTest.php b/Tests/Pgsql/PgsqlQueryTest.php index 7db376f26..a32a4eb01 100644 --- a/Tests/Pgsql/PgsqlQueryTest.php +++ b/Tests/Pgsql/PgsqlQueryTest.php @@ -45,14 +45,6 @@ protected function setUp(): void $this->query = new PgsqlQuery($this->db); } - /** - * @testdox A string is cast as a character string for the driver - */ - public function testCastAsChar() - { - $this->assertSame('foo::text', $this->query->castAsChar('foo')); - } - /** * @testdox A string is cast as a character string for the driver */ diff --git a/Tests/Query/QueryElementTest.php b/Tests/Query/QueryElementTest.php index 0b37877d7..7fa6eccfb 100644 --- a/Tests/Query/QueryElementTest.php +++ b/Tests/Query/QueryElementTest.php @@ -7,6 +7,7 @@ namespace Joomla\Database\Tests\Query; use Joomla\Database\Query\QueryElement; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** @@ -24,33 +25,35 @@ class QueryElementTest extends TestCase * glue => glue * - array $expected values in same array format * - * @return \Generator + * @return array */ - public function dataInstantiation(): \Generator + public static function dataInstantiation(): array { - yield 'array-element' => [ - [ - 'name' => 'FROM', - 'elements' => ['field1', 'field2'], - 'glue' => ',', - ], - [ - 'name' => 'FROM', - 'elements' => ['field1', 'field2'], - 'glue' => ',', + return [ + 'array-element' => [ + [ + 'name' => 'FROM', + 'elements' => ['field1', 'field2'], + 'glue' => ',', + ], + [ + 'name' => 'FROM', + 'elements' => ['field1', 'field2'], + 'glue' => ',', + ], ], - ]; - yield 'non-array-element' => [ - [ - 'name' => 'TABLE', - 'elements' => 'my_table_name', - 'glue' => ',', - ], - [ - 'name' => 'TABLE', - 'elements' => ['my_table_name'], - 'glue' => ',', + 'non-array-element' => [ + [ + 'name' => 'TABLE', + 'elements' => 'my_table_name', + 'glue' => ',', + ], + [ + 'name' => 'TABLE', + 'elements' => ['my_table_name'], + 'glue' => ',', + ], ], ]; } @@ -60,9 +63,8 @@ public function dataInstantiation(): \Generator * * @param array $element values for base element * @param array $expected values for expected fields - * - * @dataProvider dataInstantiation */ + #[DataProvider('dataInstantiation')] public function testInstantiation(array $element, array $expected) { $baseElement = new QueryElement($element['name'], $element['elements'], $element['glue']); @@ -92,36 +94,38 @@ public function testInstantiation(array $element, array $expected) * - string $glue the element glue * - string $expected expected result * - * @return \Generator + * @return array */ - public function dataCastingToString(): \Generator + public static function dataCastingToString(): array { - yield [ - 'FROM', - 'table1', - ',', - PHP_EOL . 'FROM table1', - ]; + return [ + [ + 'FROM', + 'table1', + ',', + PHP_EOL . 'FROM table1', + ], - yield [ - 'SELECT', - ['column1', 'column2'], - ',', - PHP_EOL . 'SELECT column1,column2', - ]; + [ + 'SELECT', + ['column1', 'column2'], + ',', + PHP_EOL . 'SELECT column1,column2', + ], - yield [ - '()', - ['column1', 'column2'], - ',', - PHP_EOL . '(column1,column2)', - ]; + [ + '()', + ['column1', 'column2'], + ',', + PHP_EOL . '(column1,column2)', + ], - yield [ - 'CONCAT()', - ['column1', 'column2'], - ',', - PHP_EOL . 'CONCAT(column1,column2)', + [ + 'CONCAT()', + ['column1', 'column2'], + ',', + PHP_EOL . 'CONCAT(column1,column2)', + ], ]; } @@ -132,9 +136,8 @@ public function dataCastingToString(): \Generator * @param mixed $elements String or array. * @param string $glue The glue for elements. * @param string $expected The expected value. - * - * @dataProvider dataCastingToString */ + #[DataProvider('dataCastingToString')] public function testCastingToString($name, $elements, $glue, $expected) { $this->assertThat( @@ -155,46 +158,48 @@ public function testCastingToString($name, $elements, $glue, $expected) * - array $expected array of elements that should be the value of the elements attribute after the merge * - string $string value of __toString() for element after append * - * @return \Generator + * @return array */ - public function dataAppend(): \Generator + public static function dataAppend(): array { - yield 'array-element' => [ - [ - 'name' => 'SELECT', - 'elements' => [], - 'glue' => ',', - ], - [ - 'name' => 'FROM', - 'elements' => ['my_table_name'], - 'glue' => ',', - ], - [ - 'name' => 'FROM', - 'elements' => ['my_table_name'], - 'glue' => ',', + return [ + 'array-element' => [ + [ + 'name' => 'SELECT', + 'elements' => [], + 'glue' => ',', + ], + [ + 'name' => 'FROM', + 'elements' => ['my_table_name'], + 'glue' => ',', + ], + [ + 'name' => 'FROM', + 'elements' => ['my_table_name'], + 'glue' => ',', + ], + PHP_EOL . 'SELECT ' . PHP_EOL . 'FROM my_table_name', ], - PHP_EOL . 'SELECT ' . PHP_EOL . 'FROM my_table_name', - ]; - yield 'non-array-element' => [ - [ - 'name' => 'SELECT', - 'elements' => [], - 'glue' => ',', - ], - [ - 'name' => 'FROM', - 'elements' => ['my_table_name'], - 'glue' => ',', - ], - [ - 'name' => 'FROM', - 'elements' => ['my_table_name'], - 'glue' => ',', + 'non-array-element' => [ + [ + 'name' => 'SELECT', + 'elements' => [], + 'glue' => ',', + ], + [ + 'name' => 'FROM', + 'elements' => ['my_table_name'], + 'glue' => ',', + ], + [ + 'name' => 'FROM', + 'elements' => ['my_table_name'], + 'glue' => ',', + ], + PHP_EOL . 'SELECT ' . PHP_EOL . 'FROM my_table_name', ], - PHP_EOL . 'SELECT ' . PHP_EOL . 'FROM my_table_name', ]; } @@ -205,9 +210,8 @@ public function dataAppend(): \Generator * @param array $append append element values * @param array $expected expected element values for elements field after append * @param string $string expected value of toString (not used in this test) - * - * @dataProvider dataAppend */ + #[DataProvider('dataAppend')] public function testAppend($element, $append, $expected, $string) { $baseElement = new QueryElement($element['name'], $element['elements'], $element['glue']); diff --git a/Tests/Sqlsrv/SqlsrvQueryTest.php b/Tests/Sqlsrv/SqlsrvQueryTest.php index 39b0d034d..e7819d18d 100644 --- a/Tests/Sqlsrv/SqlsrvQueryTest.php +++ b/Tests/Sqlsrv/SqlsrvQueryTest.php @@ -45,14 +45,6 @@ protected function setUp(): void $this->query = new SqlsrvQuery($this->db); } - /** - * @testdox A string is cast as a character string for the driver - */ - public function testCastAsChar() - { - $this->assertSame('CAST(foo as NVARCHAR(10))', $this->query->castAsChar('foo')); - } - /** * @testdox A string is cast as a character string for the driver */ diff --git a/Tests/Stubs/TestDatabaseExporter.php b/Tests/Stubs/TestDatabaseExporter.php new file mode 100644 index 000000000..c121d4d0f --- /dev/null +++ b/Tests/Stubs/TestDatabaseExporter.php @@ -0,0 +1,28 @@ + Date: Fri, 11 Jul 2025 11:33:07 +0200 Subject: [PATCH 12/26] Improvements from phpstan scan --- src/Command/ExportCommand.php | 28 ++++++++++++---------------- src/DatabaseDriver.php | 12 +++--------- src/Pgsql/PgsqlDriver.php | 1 + src/Pgsql/PgsqlExporter.php | 8 ++++++++ src/Pgsql/PgsqlImporter.php | 8 ++++++++ src/Query/MysqlQueryBuilder.php | 7 ++++++- src/Sqlsrv/SqlsrvDriver.php | 2 -- src/Sqlsrv/SqlsrvStatement.php | 6 +++--- 8 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/Command/ExportCommand.php b/src/Command/ExportCommand.php index ab4d172f0..b3a9c0097 100644 --- a/src/Command/ExportCommand.php +++ b/src/Command/ExportCommand.php @@ -95,7 +95,6 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in $tableName = $input->getOption('table'); $zip = $input->getOption('zip'); - $zipFile = $folderPath . '/data_exported_' . date("Y-m-d\TH-i-s") . '.zip'; $tables = $this->db->getTableList(); $prefix = $this->db->getPrefix(); @@ -109,20 +108,15 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in $tables = [$tableName]; } - if ($zip) { - if (!class_exists(Archive::class)) { - $symfonyStyle->error('The "joomla/archive" Composer package is not installed, cannot create ZIP files.'); - - return 1; - } - - /** @var Zip $zipArchive */ - $zipArchive = (new Archive())->getAdapter('zip'); + if ($zip && !class_exists(Archive::class)) { + $symfonyStyle->error('The "joomla/archive" Composer package is not installed, cannot create ZIP files.'); - $filenames = []; - $zipFilesArray = []; + return 1; } + $filenames = []; + $zipFilesArray = []; + foreach ($tables as $table) { // If an empty prefix is in use then we will dump all tables, otherwise the prefix must match if (strlen($prefix) === 0 || strpos(substr($table, 0, strlen($prefix)), $prefix) !== false) { @@ -139,16 +133,18 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in File::write($filename, $data); - if ($zip) { - $zipFilesArray[] = ['name' => $table . '.xml', 'data' => $data]; - $filenames[] = $filename; - } + $zipFilesArray[] = ['name' => $table . '.xml', 'data' => $data]; + $filenames[] = $filename; $symfonyStyle->text(sprintf('Exported data for %s in %d seconds', $table, round(microtime(true) - $taskTime, 3))); } } if ($zip) { + /** @var Zip $zipArchive */ + $zipArchive = (new Archive())->getAdapter('zip'); + + $zipFile = $folderPath . '/data_exported_' . date("Y-m-d\TH-i-s") . '.zip'; $zipArchive->create($zipFile, $zipFilesArray); foreach ($filenames as $fname) { File::delete($fname); diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php index 497d9cc0c..b56d92296 100644 --- a/src/DatabaseDriver.php +++ b/src/DatabaseDriver.php @@ -250,7 +250,6 @@ public static function getConnectors() $baseName = $file->getBasename(); // Derive the class name from the type. - /** @var DatabaseDriver $class */ $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($baseName)) . '\\' . ucfirst(strtolower($baseName)) . 'Driver'; // If the class doesn't exist, or if it's not supported on this system, move on to the next type. @@ -501,17 +500,12 @@ public function __destruct() * * @param string $dbName The database name that will be altered * - * @return boolean|resource + * @return boolean * * @since 2.0.0 - * @throws \RuntimeException */ - public function alterDbCharacterSet($dbName) + public function alterDbCharacterSet(string $dbName) { - if ($dbName === null) { - throw new \RuntimeException('Database name must not be null.'); - } - $this->setQuery($this->getAlterDbCharacterSet($dbName)); return $this->execute(); @@ -523,7 +517,7 @@ public function alterDbCharacterSet($dbName) * @param \stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * - * @return boolean|resource + * @return boolean * * @since 2.0.0 * @throws \RuntimeException diff --git a/src/Pgsql/PgsqlDriver.php b/src/Pgsql/PgsqlDriver.php index 86ee08725..16c93d12e 100644 --- a/src/Pgsql/PgsqlDriver.php +++ b/src/Pgsql/PgsqlDriver.php @@ -767,6 +767,7 @@ public function insertObject($table, &$object, $key = null) } // Create the base insert statement. + /** @var PgsqlQuery $query */ $query = $this->createQuery(); $query->insert($this->quoteName($table)) diff --git a/src/Pgsql/PgsqlExporter.php b/src/Pgsql/PgsqlExporter.php index 82d94e0d0..124081f63 100644 --- a/src/Pgsql/PgsqlExporter.php +++ b/src/Pgsql/PgsqlExporter.php @@ -18,6 +18,14 @@ */ class PgsqlExporter extends DatabaseExporter { + /** + * The database connector to use for exporting structure and/or data. + * + * @var PgsqlDriver + * @since 1.0 + */ + protected $db; + /** * Builds the XML data for the tables to export. * diff --git a/src/Pgsql/PgsqlImporter.php b/src/Pgsql/PgsqlImporter.php index 1bc7d85f6..207c25d77 100644 --- a/src/Pgsql/PgsqlImporter.php +++ b/src/Pgsql/PgsqlImporter.php @@ -18,6 +18,14 @@ */ class PgsqlImporter extends DatabaseImporter { + /** + * The database connector to use for exporting structure and/or data. + * + * @var PgsqlDriver + * @since 1.0 + */ + protected $db; + /** * Checks if all data and options are in order prior to exporting. * diff --git a/src/Query/MysqlQueryBuilder.php b/src/Query/MysqlQueryBuilder.php index a23f6f805..1ce5000be 100644 --- a/src/Query/MysqlQueryBuilder.php +++ b/src/Query/MysqlQueryBuilder.php @@ -9,6 +9,8 @@ namespace Joomla\Database\Query; +use Joomla\Database\Mysql\MysqlDriver; + /** * Trait for MySQL Query Building. * @@ -217,8 +219,11 @@ public function findInSet($value, $set) */ public function selectRowNumber($orderBy, $orderColumnAlias) { + /** @var MysqlDriver $db */ + $db = $this->db; + // Use parent method with ROW_NUMBER() window function on MariaDB >= 10.2.0 and MySQL >= 8.0.0. - if (version_compare($this->db->getVersion(), $this->db->isMariaDb() ? '10.2.0' : '8.0.0', '>=')) { + if (version_compare($db->getVersion(), $db->isMariaDb() ? '10.2.0' : '8.0.0', '>=')) { return parent::selectRowNumber($orderBy, $orderColumnAlias); } diff --git a/src/Sqlsrv/SqlsrvDriver.php b/src/Sqlsrv/SqlsrvDriver.php index d8b88cb80..dadbb27a6 100644 --- a/src/Sqlsrv/SqlsrvDriver.php +++ b/src/Sqlsrv/SqlsrvDriver.php @@ -338,7 +338,6 @@ public function getCollation() * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. * * @since 1.6.0 - * @throws \RuntimeException */ public function getConnectionCollation() { @@ -352,7 +351,6 @@ public function getConnectionCollation() * @return string The database encryption details. * * @since 2.0.0 - * @throws \RuntimeException */ public function getConnectionEncryption(): string { diff --git a/src/Sqlsrv/SqlsrvStatement.php b/src/Sqlsrv/SqlsrvStatement.php index e93599fe5..096c7d422 100644 --- a/src/Sqlsrv/SqlsrvStatement.php +++ b/src/Sqlsrv/SqlsrvStatement.php @@ -44,7 +44,7 @@ class SqlsrvStatement implements StatementInterface /** * The default class to use for building object result sets. * - * @var integer + * @var string * @since 2.0.0 */ protected $defaultObjectClass = \stdClass::class; @@ -80,7 +80,7 @@ class SqlsrvStatement implements StatementInterface /** * The prepared statement. * - * @var resource + * @var ?resource * @since 2.0.0 */ protected $statement; @@ -352,7 +352,7 @@ public function errorCode() return $errors[0]['code']; } - return false; + return ''; } /** From bdaa08b1962e6d300b4d7fec76abee64bd5c30bd Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 11 Jul 2025 11:33:29 +0200 Subject: [PATCH 13/26] Cleanup .gitattributes --- .gitattributes | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index f52ca8448..a77d5a71d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,14 +1,11 @@ .github/ export-ignore -.phan/ export-ignore docs/ export-ignore Tests/ export-ignore -.appveyor.yml export-ignore -.drone.jsonnet export-ignore -.drone.yml export-ignore .editorconfig export-ignore .git-blame-ignore-revs export-ignore .gitattributes export-ignore .gitignore export-ignore +phpstan.neon export-ignore phpunit.*.xml.dist export-ignore phpunit.xml.dist export-ignore ruleset.xml export-ignore From cf27ee7a6d7a7431e63cb131270b4136f2e518f4 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Sun, 13 Jul 2025 12:57:10 +0200 Subject: [PATCH 14/26] Updating Github Actions PHP versions --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc7faa1d0..f302f38b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: composer: name: Install PHP dependencies runs-on: ubuntu-latest - container: joomlaprojects/docker-images:php8.1 + container: joomlaprojects/docker-images:php8.3 steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 @@ -32,7 +32,7 @@ jobs: code-style-php: name: Check PHP code style runs-on: ubuntu-latest - container: joomlaprojects/docker-images:php8.1 + container: joomlaprojects/docker-images:php8.3 needs: [composer] steps: - uses: actions/checkout@v4 @@ -68,7 +68,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3', '8.4'] + php_version: ['8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Run Unit tests @@ -84,7 +84,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3', '8.4'] + php_version: ['8.3', '8.4'] db_engine: ['mysql', 'mysqli'] db_version: ['5.7', '8.0'] steps: @@ -110,7 +110,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3', '8.4'] + php_version: ['8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Run Unit tests @@ -134,7 +134,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3', '8.4'] + php_version: ['8.3', '8.4'] db_version: ['10', '11'] steps: - uses: actions/checkout@v4 @@ -158,7 +158,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3', '8.4'] + php_version: ['8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Run Unit tests @@ -190,7 +190,7 @@ jobs: needs: [code-style-php] strategy: matrix: - php_version: ['8.1', '8.2', '8.3', '8.4'] + php_version: ['8.3', '8.4'] steps: - uses: actions/checkout@v4 - name: Setup PHP From c1c92c8820e29c8f8ff0baaf76734d17f2639658 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Sun, 13 Jul 2025 15:30:38 +0200 Subject: [PATCH 15/26] Updating unittests to phpunit 12 - part 2 --- Tests/AbstractDatabaseDriverTestCase.php | 42 +++-- Tests/Mysql/MysqlDriverTest.php | 204 +++++++++++----------- Tests/Mysql/MysqlExporterTest.php | 82 ++++----- Tests/Mysql/MysqlImporterTest.php | 197 ++++++++++----------- Tests/Mysql/MysqlQueryTest.php | 14 +- Tests/Mysqli/MysqliDriverTest.php | 204 +++++++++++----------- Tests/Mysqli/MysqliExporterTest.php | 81 ++++----- Tests/Mysqli/MysqliImporterTest.php | 197 ++++++++++----------- Tests/Mysqli/MysqliQueryTest.php | 14 +- Tests/Pgsql/PgsqlDriverTest.php | 196 +++++++++++---------- Tests/Pgsql/PgsqlExporterTest.php | 81 ++++----- Tests/Pgsql/PgsqlImporterTest.php | 209 ++++++++++++----------- Tests/Pgsql/PgsqlQueryTest.php | 27 +-- Tests/Sqlite/SqliteDriverTest.php | 168 +++++++++--------- Tests/Sqlite/SqliteQueryTest.php | 27 +-- Tests/Sqlsrv/SqlsrvDriverTest.php | 134 ++++++++------- Tests/Sqlsrv/SqlsrvQueryTest.php | 27 +-- 17 files changed, 995 insertions(+), 909 deletions(-) diff --git a/Tests/AbstractDatabaseDriverTestCase.php b/Tests/AbstractDatabaseDriverTestCase.php index 2d7ea2b11..b4377cf7a 100644 --- a/Tests/AbstractDatabaseDriverTestCase.php +++ b/Tests/AbstractDatabaseDriverTestCase.php @@ -13,6 +13,7 @@ use Joomla\Database\ParameterType; use Joomla\Database\QueryInterface; use Joomla\Test\DatabaseTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * Base test class for Joomla\Database\DatabaseDriver @@ -72,13 +73,15 @@ public function testIsConnectionEncryptionSupported() /** * Data provider for table dropping test cases * - * @return \Generator + * @return array */ - public function dataDropTable() + public static function dataDropTable(): array { - yield 'database exists before query' => ['#__dbtest', true]; + return [ + 'database exists before query' => ['#__dbtest', true], - yield 'database does not exist before query' => ['#__foo', false]; + 'database does not exist before query' => ['#__foo', false], + ]; } /** @@ -86,9 +89,8 @@ public function dataDropTable() * * @param string $table The name of the database table to drop. * @param boolean $alreadyExists Flag indicating the table should exist before the DROP TABLE query. - * - * @dataProvider dataDropTable */ + #[DataProvider('dataDropTable')] public function testDropTable(string $table, bool $alreadyExists) { $this->assertSame( @@ -110,9 +112,9 @@ public function testDropTable(string $table, bool $alreadyExists) /** * Data provider for escaping test cases * - * @return \Generator + * @return array */ - abstract public function dataEscape(): \Generator; + abstract public static function dataEscape(): array; /** * @testdox Text can be escaped @@ -120,9 +122,8 @@ abstract public function dataEscape(): \Generator; * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * @param string $expected The expected result. - * - * @dataProvider dataEscape */ + #[DataProvider('dataEscape')] public function testEscape($text, $extra, $expected) { $this->assertSame( @@ -240,9 +241,9 @@ public function testGetIterator() /** * Data provider for fetching table column test cases * - * @return \Generator + * @return array */ - abstract public function dataGetTableColumns(): \Generator; + abstract public static function dataGetTableColumns(): array; /** * @testdox Information about the columns of a database table is returned @@ -250,9 +251,8 @@ abstract public function dataGetTableColumns(): \Generator; * @param string $table The name of the database table. * @param boolean $typeOnly True (default) to only return field types. * @param array $expected Expected result. - * - * @dataProvider dataGetTableColumns */ + #[DataProvider('dataGetTableColumns')] public function testGetTableColumns(string $table, bool $typeOnly, array $expected) { $this->assertEquals( @@ -596,18 +596,17 @@ public function testLockAndUnlockTable() /** * Data provider for binary quoting test cases * - * @return \Generator + * @return array */ - abstract public function dataQuoteBinary(): \Generator; + abstract public static function dataQuoteBinary(): array; /** * @testdox A binary value is quoted properly * * @param string $data The binary quoted input string. * @param string $expected The expected result. - * - * @dataProvider dataQuoteBinary */ + #[DataProvider('dataQuoteBinary')] public function testQuoteBinary($data, $expected) { $this->assertSame($expected, static::$connection->quoteBinary($data)); @@ -616,9 +615,9 @@ public function testQuoteBinary($data, $expected) /** * Data provider for name quoting test cases * - * @return \Generator + * @return array */ - abstract public function dataQuoteName(): \Generator; + abstract public static function dataQuoteName(): array; /** * @testdox A value is name quoted properly @@ -626,9 +625,8 @@ abstract public function dataQuoteName(): \Generator; * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * @param array|string $as The AS query part associated to $name. * @param array|string $expected The expected result. - * - * @dataProvider dataQuoteName */ + #[DataProvider('dataQuoteName')] public function testQuoteName($name, $as, $expected) { $this->assertSame( diff --git a/Tests/Mysql/MysqlDriverTest.php b/Tests/Mysql/MysqlDriverTest.php index 47d68214d..feae93579 100644 --- a/Tests/Mysql/MysqlDriverTest.php +++ b/Tests/Mysql/MysqlDriverTest.php @@ -14,6 +14,7 @@ use Joomla\Database\Mysql\MysqlQuery; use Joomla\Database\ParameterType; use Joomla\Database\Tests\AbstractDatabaseDriverTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * Test class for Joomla\Database\Mysql\MysqlDriver @@ -91,9 +92,9 @@ protected function tearDown(): void /** * Data provider for fetching table column test cases * - * @return \Generator + * @return array */ - public function dataGetTableColumns(): \Generator + public static function dataGetTableColumns(): array { // For unknown reasons, the connection gets lost on Travis. re-establish, if that happens if (static::$connection === null) { @@ -103,76 +104,78 @@ public function dataGetTableColumns(): \Generator $isMySQL8 = !static::$connection->isMariaDb() && version_compare(static::$connection->getVersion(), '8.0', '>='); $useDisplayWidth = static::$connection->isMariaDb() || version_compare(static::$connection->getVersion(), '8.0.17', '<'); - yield 'only column types' => [ - '#__dbtest', - true, - [ - 'id' => 'int unsigned', - 'title' => 'varchar', - 'start_date' => 'datetime', - 'description' => 'text', - 'data' => 'blob', + return [ + 'only column types' => [ + '#__dbtest', + true, + [ + 'id' => 'int unsigned', + 'title' => 'varchar', + 'start_date' => 'datetime', + 'description' => 'text', + 'data' => 'blob', + ], ], - ]; - yield 'full column information' => [ - '#__dbtest', - false, - [ - 'id' => (object) [ - 'Field' => 'id', - 'Type' => $useDisplayWidth ? 'int(10) unsigned' : 'int unsigned', - 'Collation' => $isMySQL8 ? null : '', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => $isMySQL8 ? null : '', - 'Extra' => 'auto_increment', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'title' => (object) [ - 'Field' => 'title', - 'Type' => 'varchar(50)', - 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', - 'Null' => 'NO', - 'Key' => '', - 'Default' => $isMySQL8 ? null : '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'start_date' => (object) [ - 'Field' => 'start_date', - 'Type' => 'datetime', - 'Collation' => '', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'description' => (object) [ - 'Field' => 'description', - 'Type' => 'text', - 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', - 'Null' => 'NO', - 'Key' => '', - 'Default' => $isMySQL8 ? null : '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'data' => (object) [ - 'Field' => 'data', - 'Type' => 'blob', - 'Collation' => '', - 'Null' => 'YES', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', + 'full column information' => [ + '#__dbtest', + false, + [ + 'id' => (object) [ + 'Field' => 'id', + 'Type' => $useDisplayWidth ? 'int(10) unsigned' : 'int unsigned', + 'Collation' => $isMySQL8 ? null : '', + 'Null' => 'NO', + 'Key' => 'PRI', + 'Default' => $isMySQL8 ? null : '', + 'Extra' => 'auto_increment', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'title' => (object) [ + 'Field' => 'title', + 'Type' => 'varchar(50)', + 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', + 'Null' => 'NO', + 'Key' => '', + 'Default' => $isMySQL8 ? null : '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'start_date' => (object) [ + 'Field' => 'start_date', + 'Type' => 'datetime', + 'Collation' => '', + 'Null' => 'NO', + 'Key' => '', + 'Default' => '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'description' => (object) [ + 'Field' => 'description', + 'Type' => 'text', + 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', + 'Null' => 'NO', + 'Key' => '', + 'Default' => $isMySQL8 ? null : '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'data' => (object) [ + 'Field' => 'data', + 'Type' => 'blob', + 'Collation' => '', + 'Null' => 'YES', + 'Key' => '', + 'Default' => '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], ], ], ]; @@ -181,50 +184,58 @@ public function dataGetTableColumns(): \Generator /** * Data provider for escaping test cases * - * @return \Generator + * @return array */ - public function dataEscape(): \Generator + public static function dataEscape(): array { - yield ["'%_abc123", false, '\\\'%_abc123']; - yield ["'%_abc123", true, '\\\'\\%\_abc123']; - yield [3, false, 3]; - yield [3.14, false, '3.14']; + return [ + ["'%_abc123", false, '\\\'%_abc123'], + ["'%_abc123", true, '\\\'\\%\_abc123'], + [3, false, 3], + [3.14, false, '3.14'], + ]; } /** * Data provider for table dropping test cases * - * @return \Generator + * @return array */ - public function dataDropTable() + public static function dataDropTable(): array { - yield 'database exists before query' => ['#__dbtest', true]; + return [ + 'database exists before query' => ['#__dbtest', true], - yield 'database does not exist before query' => ['#__foo', false]; + 'database does not exist before query' => ['#__foo', false], + ]; } /** * Data provider for binary quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteBinary(): \Generator + public static function dataQuoteBinary(): array { - yield ['DATA', "X'" . bin2hex('DATA') . "'"]; - yield ["\x00\x01\x02\xff", "X'000102ff'"]; - yield ["\x01\x01\x02\xff", "X'010102ff'"]; + return [ + ['DATA', "X'" . bin2hex('DATA') . "'"], + ["\x00\x01\x02\xff", "X'000102ff'"], + ["\x01\x01\x02\xff", "X'010102ff'"], + ]; } /** * Data provider for name quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteName(): \Generator + public static function dataQuoteName(): array { - yield ['protected`title', null, '`protected``title`']; - yield ['protected"title', null, '`protected"title`']; - yield ['protected]title', null, '`protected]title`']; + return [ + ['protected`title', null, '`protected``title`'], + ['protected"title', null, '`protected"title`'], + ['protected]title', null, '`protected]title`'], + ]; } /* @@ -381,13 +392,15 @@ public function testTransactionCommit() /** * Data provider for transaction rollback test cases * - * @return \Generator + * @return array */ - public function dataTransactionRollback() + public static function dataTransactionRollback(): array { - yield 'rollback without savepoint' => [null, 0]; + return [ + 'rollback without savepoint' => [null, 0], - yield 'rollback with savepoint' => ['transactionSavepoint', 1]; + 'rollback with savepoint' => ['transactionSavepoint', 1], + ]; } /** @@ -395,9 +408,8 @@ public function dataTransactionRollback() * * @param string|null $toSavepoint Savepoint name to rollback transaction to * @param integer $tupleCount Number of tuples found after insertion and rollback - * - * @dataProvider dataTransactionRollback */ + #[DataProvider('dataTransactionRollback')] public function testTransactionRollback(?string $toSavepoint, int $tupleCount) { $this->loadExampleData(); diff --git a/Tests/Mysql/MysqlExporterTest.php b/Tests/Mysql/MysqlExporterTest.php index f44d075c7..f7c4488fe 100644 --- a/Tests/Mysql/MysqlExporterTest.php +++ b/Tests/Mysql/MysqlExporterTest.php @@ -10,6 +10,7 @@ use Joomla\Database\Mysql\MysqlDriver; use Joomla\Database\Mysql\MysqlExporter; use Joomla\Database\Mysql\MysqlQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -120,11 +121,12 @@ function ($name, $as = null) { /** * Data provider for string casting test cases * - * @return \Generator + * @return array */ - public function dataCastingToString(): \Generator + public static function dataCastingToString(): array { - yield 'without structure or data' => [ + return [ + 'without structure or data' => [ false, false, << XML , - ]; + ], - yield 'with only structure' => [ + 'with only structure' => [ true, false, << XML , - ]; + ], - yield 'with only data' => [ + 'with only data' => [ false, true, << XML , - ]; + ], - yield 'with structure and data' => [ + 'with structure and data' => [ true, true, << XML , + ], ]; } @@ -209,9 +212,8 @@ public function dataCastingToString(): \Generator * @param boolean $withStructure True to export the structure, false to not. * @param boolean $withData True to export the data, false to not. * @param string $expectedXml Expected XML string. - * - * @dataProvider dataCastingToString */ + #[DataProvider('dataCastingToString')] public function testCastingToString(bool $withStructure, bool $withData, string $expectedXml) { $exporter = new MysqlExporter(); @@ -244,45 +246,46 @@ public function testCastingToString(bool $withStructure, bool $withData, string /** * Data provider for check test cases * - * @return \Generator + * @return array */ - public function dataCheck(): \Generator + public static function dataCheck(): array { - yield 'passes checks' => [ - $this->createMock(MysqlDriver::class), - '#__dbtest', - null, - ]; + return [ + 'passes checks' => [ + MysqlDriver::class, + '#__dbtest', + null, + ], - yield 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), - '#__dbtest', - 'Database connection wrong type.', - ]; + 'fails checks with incorrect database driver subclass' => [ + DatabaseInterface::class, + '#__dbtest', + 'Database connection wrong type.', + ], - yield 'fails checks with no database driver' => [ - null, - '#__dbtest', - 'Database connection wrong type.', - ]; + 'fails checks with no database driver' => [ + null, + '#__dbtest', + 'Database connection wrong type.', + ], - yield 'fails checks with no tables' => [ - $this->createMock(MysqlDriver::class), - null, - 'ERROR: No Tables Specified', + 'fails checks with no tables' => [ + MysqlDriver::class, + null, + 'ERROR: No Tables Specified', + ], ]; } /** * @testdox The exporter checks for errors * - * @param DatabaseInterface|null $db Database driver to set in the exporter. - * @param string[]|string|null $from Database tables to export from. - * @param string|null $exceptionMessage If an Exception should be thrown, the expected message - * - * @dataProvider dataCheck + * @param string|null $db Database driver to set in the exporter. + * @param string[]|string|null $from Database tables to export from. + * @param string|null $exceptionMessage If an Exception should be thrown, the expected message */ - public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessage) + #[DataProvider('dataCheck')] + public function testCheck(?string $db, $from, ?string $exceptionMessage) { if ($exceptionMessage) { $this->expectException(\RuntimeException::class); @@ -292,10 +295,11 @@ public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessa $exporter = new MysqlExporter(); if ($db) { - $exporter->setDbo($db); + $exporter->setDbo($this->createMock($db)); } if ($from) { + $exporter->from($from); } diff --git a/Tests/Mysql/MysqlImporterTest.php b/Tests/Mysql/MysqlImporterTest.php index d9484a306..512d44393 100644 --- a/Tests/Mysql/MysqlImporterTest.php +++ b/Tests/Mysql/MysqlImporterTest.php @@ -10,6 +10,7 @@ use Joomla\Database\Mysql\MysqlDriver; use Joomla\Database\Mysql\MysqlImporter; use Joomla\Database\Mysql\MysqlQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -205,9 +206,9 @@ protected function tearDown(): void /** * Data provider for import test cases * - * @return \Generator + * @return array */ - public function dataImport(): \Generator + public static function dataImport(): array { $idField = ''; $titleField = ''; @@ -216,87 +217,89 @@ public function dataImport(): \Generator $idKey = ''; $titleKey = ''; - yield 'no changes in existing structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . ''), - [], - [], - ]; + return [ + 'no changes in existing structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . ''), + [], + [], + ], - yield 'inserts row into database' => [ - true, - true, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . ' 1Testing'), - [], - [ - 'jos_dbtest' => [ - (object) [ - 'id' => '1', - 'title' => 'Testing', + 'inserts row into database' => [ + true, + true, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . ' 1Testing'), + [], + [ + 'jos_dbtest' => [ + (object) [ + 'id' => '1', + 'title' => 'Testing', + ], ], ], ], - ]; - yield 'adds alias column to the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $aliasField . $idKey . ''), - [ - "ALTER TABLE `jos_dbtest` ADD COLUMN `alias` varchar(255) NOT NULL DEFAULT ''", + 'adds alias column to the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $aliasField . $idKey . ''), + [ + "ALTER TABLE `jos_dbtest` ADD COLUMN `alias` varchar(255) NOT NULL DEFAULT ''", + ], + [], ], - [], - ]; - yield 'adds key for the title column to the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . $titleKey . ''), - [ - 'ALTER TABLE `jos_dbtest` ADD UNIQUE KEY `idx_title` (`title`)', + 'adds key for the title column to the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . $titleKey . ''), + [ + 'ALTER TABLE `jos_dbtest` ADD UNIQUE KEY `idx_title` (`title`)', + ], + [], ], - [], - ]; - yield 'removes the title column from the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $idKey . ''), - [ - 'ALTER TABLE `jos_dbtest` DROP COLUMN `title`', + 'removes the title column from the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $idKey . ''), + [ + 'ALTER TABLE `jos_dbtest` DROP COLUMN `title`', + ], + [], ], - [], - ]; - yield 'removes the primary key based on the id column from the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . ''), - [ - 'ALTER TABLE `jos_dbtest` DROP PRIMARY KEY', + 'removes the primary key based on the id column from the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . ''), + [ + 'ALTER TABLE `jos_dbtest` DROP PRIMARY KEY', + ], + [], ], - [], - ]; - yield 'adds a new database table' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . '' . $idField . $titleField . $idKey . ''), - [ - "CREATE TABLE `#__newtest` (`id` int(11) unsigned NOT NULL DEFAULT '' AUTO_INCREMENT, `title` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`))", + 'adds a new database table' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . '' . $idField . $titleField . $idKey . ''), + [ + "CREATE TABLE `#__newtest` (`id` int(11) unsigned NOT NULL DEFAULT '' AUTO_INCREMENT, `title` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`))", + ], + [], ], - [], - ]; - yield 'changes the field type of the id field' => [ - true, - false, - new \SimpleXMLElement('' . $titleField . $idKey . ''), - [ - "ALTER TABLE `jos_dbtest` CHANGE COLUMN `id` `id` bigint() unsigned NOT NULL DEFAULT '' AUTO_INCREMENT", + 'changes the field type of the id field' => [ + true, + false, + new \SimpleXMLElement('' . $titleField . $idKey . ''), + [ + "ALTER TABLE `jos_dbtest` CHANGE COLUMN `id` `id` bigint() unsigned NOT NULL DEFAULT '' AUTO_INCREMENT", + ], + [], ], - [], ]; } @@ -308,9 +311,8 @@ public function dataImport(): \Generator * @param \SimpleXMLElement $from XML document to import. * @param string[] $expectedQueries The expected database queries to perform. * @param string[] $expectedInsertObjects The expected objects to be given to the database's insertObject method. - * - * @dataProvider dataImport */ + #[DataProvider('dataImport')] public function testImport(bool $mergeStructure, bool $importData, \SimpleXMLElement $from, array $expectedQueries, array $expectedInsertObjects) { $importer = new MysqlImporter(); @@ -332,45 +334,46 @@ public function testImport(bool $mergeStructure, bool $importData, \SimpleXMLEle /** * Data provider for check test cases * - * @return \Generator + * @return array */ - public function dataCheck(): \Generator + public static function dataCheck(): array { - yield 'passes checks' => [ - $this->createMock(MysqlDriver::class), - '#__dbtest', - null, - ]; + return [ + 'passes checks' => [ + MysqlDriver::class, + '#__dbtest', + null, + ], - yield 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), - new \SimpleXMLElement(''), - 'Database connection wrong type.', - ]; + 'fails checks with incorrect database driver subclass' => [ + DatabaseInterface::class, + new \SimpleXMLElement(''), + 'Database connection wrong type.', + ], - yield 'fails checks with no database driver' => [ - null, - new \SimpleXMLElement(''), - 'Database connection wrong type.', - ]; + 'fails checks with no database driver' => [ + null, + new \SimpleXMLElement(''), + 'Database connection wrong type.', + ], - yield 'fails checks with no tables' => [ - $this->createMock(MysqlDriver::class), - null, - 'ERROR: No Tables Specified', + 'fails checks with no tables' => [ + MysqlDriver::class, + null, + 'ERROR: No Tables Specified', + ], ]; } /** * @testdox The importer checks for errors * - * @param DatabaseInterface|null $db Database driver to set in the importer. - * @param string[]|string|null $from Database structure to import. - * @param string|null $exceptionMessage If an Exception should be thrown, the expected message - * - * @dataProvider dataCheck + * @param string|null $db Database driver to set in the importer. + * @param string[]|string|null $from Database structure to import. + * @param string|null $exceptionMessage If an Exception should be thrown, the expected message */ - public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessage) + #[DataProvider('dataCheck')] + public function testCheck(?string $db, $from, ?string $exceptionMessage) { if ($exceptionMessage) { $this->expectException(\RuntimeException::class); @@ -380,7 +383,7 @@ public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessa $importer = new MysqlImporter(); if ($db) { - $importer->setDbo($db); + $importer->setDbo($this->createMock($db)); } if ($from) { diff --git a/Tests/Mysql/MysqlQueryTest.php b/Tests/Mysql/MysqlQueryTest.php index f8e6e4d0f..a9d96c621 100644 --- a/Tests/Mysql/MysqlQueryTest.php +++ b/Tests/Mysql/MysqlQueryTest.php @@ -8,6 +8,7 @@ use Joomla\Database\DatabaseInterface; use Joomla\Database\Mysql\MysqlQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -48,12 +49,14 @@ protected function setUp(): void /** * Data provider for concatenate test cases * - * @return \Generator + * @return array */ - public function dataConcatenate(): \Generator + public static function dataConcatenate(): array { - yield 'values without separator' => [['foo', 'bar'], null, 'CONCAT(foo,bar)']; - yield 'values with separator' => [['foo', 'bar'], ' and ', "CONCAT_WS(' and ', foo, bar)"]; + return [ + 'values without separator' => [['foo', 'bar'], null, 'CONCAT(foo,bar)'], + 'values with separator' => [['foo', 'bar'], ' and ', "CONCAT_WS(' and ', foo, bar)"], + ]; } /** @@ -62,9 +65,8 @@ public function dataConcatenate(): \Generator * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * @param string $expected The expected query string. - * - * @dataProvider dataConcatenate */ + #[DataProvider('dataConcatenate')] public function testConcatenate(array $values, ?string $separator, string $expected) { $this->db->expects($this->any()) diff --git a/Tests/Mysqli/MysqliDriverTest.php b/Tests/Mysqli/MysqliDriverTest.php index e23422398..59d5d33da 100644 --- a/Tests/Mysqli/MysqliDriverTest.php +++ b/Tests/Mysqli/MysqliDriverTest.php @@ -15,6 +15,7 @@ use Joomla\Database\Mysqli\MysqliQuery; use Joomla\Database\ParameterType; use Joomla\Database\Tests\AbstractDatabaseDriverTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * Test class for Joomla\Database\Mysqli\MysqliDriver @@ -95,22 +96,24 @@ protected function tearDown(): void /** * Data provider for escaping test cases * - * @return \Generator + * @return array */ - public function dataEscape(): \Generator + public static function dataEscape(): array { - yield ["'%_abc123", false, '\\\'%_abc123']; - yield ["'%_abc123", true, '\\\'\\%\_abc123']; - yield [3, false, 3]; - yield [3.14, false, '3.14']; + return [ + ["'%_abc123", false, '\\\'%_abc123'], + ["'%_abc123", true, '\\\'\\%\_abc123'], + [3, false, 3], + [3.14, false, '3.14'], + ]; } /** * Data provider for fetching table column test cases * - * @return \Generator + * @return array */ - public function dataGetTableColumns(): \Generator + public static function dataGetTableColumns(): array { // For unknown reasons, the connection gets lost on Travis. re-establish, if that happens if (static::$connection === null) { @@ -120,76 +123,78 @@ public function dataGetTableColumns(): \Generator $isMySQL8 = !static::$connection->isMariaDb() && version_compare(static::$connection->getVersion(), '8.0', '>='); $useDisplayWidth = static::$connection->isMariaDb() || version_compare(static::$connection->getVersion(), '8.0.17', '<'); - yield 'only column types' => [ - '#__dbtest', - true, - [ - 'id' => 'int unsigned', - 'title' => 'varchar', - 'start_date' => 'datetime', - 'description' => 'text', - 'data' => 'blob', + return [ + 'only column types' => [ + '#__dbtest', + true, + [ + 'id' => 'int unsigned', + 'title' => 'varchar', + 'start_date' => 'datetime', + 'description' => 'text', + 'data' => 'blob', + ], ], - ]; - yield 'full column information' => [ - '#__dbtest', - false, - [ - 'id' => (object) [ - 'Field' => 'id', - 'Type' => $useDisplayWidth ? 'int(10) unsigned' : 'int unsigned', - 'Collation' => $isMySQL8 ? null : '', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => $isMySQL8 ? null : '', - 'Extra' => 'auto_increment', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'title' => (object) [ - 'Field' => 'title', - 'Type' => 'varchar(50)', - 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', - 'Null' => 'NO', - 'Key' => '', - 'Default' => $isMySQL8 ? null : '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'start_date' => (object) [ - 'Field' => 'start_date', - 'Type' => 'datetime', - 'Collation' => '', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'description' => (object) [ - 'Field' => 'description', - 'Type' => 'text', - 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', - 'Null' => 'NO', - 'Key' => '', - 'Default' => $isMySQL8 ? null : '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', - ], - 'data' => (object) [ - 'Field' => 'data', - 'Type' => 'blob', - 'Collation' => '', - 'Null' => 'YES', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - 'Privileges' => 'select,insert,update,references', - 'Comment' => '', + 'full column information' => [ + '#__dbtest', + false, + [ + 'id' => (object) [ + 'Field' => 'id', + 'Type' => $useDisplayWidth ? 'int(10) unsigned' : 'int unsigned', + 'Collation' => $isMySQL8 ? null : '', + 'Null' => 'NO', + 'Key' => 'PRI', + 'Default' => $isMySQL8 ? null : '', + 'Extra' => 'auto_increment', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'title' => (object) [ + 'Field' => 'title', + 'Type' => 'varchar(50)', + 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', + 'Null' => 'NO', + 'Key' => '', + 'Default' => $isMySQL8 ? null : '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'start_date' => (object) [ + 'Field' => 'start_date', + 'Type' => 'datetime', + 'Collation' => '', + 'Null' => 'NO', + 'Key' => '', + 'Default' => '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'description' => (object) [ + 'Field' => 'description', + 'Type' => 'text', + 'Collation' => $isMySQL8 ? 'utf8mb3_general_ci' : 'utf8_general_ci', + 'Null' => 'NO', + 'Key' => '', + 'Default' => $isMySQL8 ? null : '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], + 'data' => (object) [ + 'Field' => 'data', + 'Type' => 'blob', + 'Collation' => '', + 'Null' => 'YES', + 'Key' => '', + 'Default' => '', + 'Extra' => '', + 'Privileges' => 'select,insert,update,references', + 'Comment' => '', + ], ], ], ]; @@ -198,37 +203,43 @@ public function dataGetTableColumns(): \Generator /** * Data provider for table dropping test cases * - * @return \Generator + * @return array */ - public function dataDropTable() + public static function dataDropTable(): array { - yield 'database exists before query' => ['#__dbtest', true]; + return [ + 'database exists before query' => ['#__dbtest', true], - yield 'database does not exist before query' => ['#__foo', false]; + 'database does not exist before query' => ['#__foo', false], + ]; } /** * Data provider for binary quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteBinary(): \Generator + public static function dataQuoteBinary(): array { - yield ['DATA', "X'" . bin2hex('DATA') . "'"]; - yield ["\x00\x01\x02\xff", "X'000102ff'"]; - yield ["\x01\x01\x02\xff", "X'010102ff'"]; + return [ + ['DATA', "X'" . bin2hex('DATA') . "'"], + ["\x00\x01\x02\xff", "X'000102ff'"], + ["\x01\x01\x02\xff", "X'010102ff'"], + ]; } /** * Data provider for name quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteName(): \Generator + public static function dataQuoteName(): array { - yield ['protected`title', null, '`protected``title`']; - yield ['protected"title', null, '`protected"title`']; - yield ['protected]title', null, '`protected]title`']; + return [ + ['protected`title', null, '`protected``title`'], + ['protected"title', null, '`protected"title`'], + ['protected]title', null, '`protected]title`'], + ]; } /* @@ -389,13 +400,15 @@ public function testTransactionCommit() /** * Data provider for transaction rollback test cases * - * @return \Generator + * @return array */ - public function dataTransactionRollback() + public static function dataTransactionRollback(): array { - yield 'rollback without savepoint' => [null, 0]; + return [ + 'rollback without savepoint' => [null, 0], - yield 'rollback with savepoint' => ['transactionSavepoint', 1]; + 'rollback with savepoint' => ['transactionSavepoint', 1], + ]; } /** @@ -403,9 +416,8 @@ public function dataTransactionRollback() * * @param string|null $toSavepoint Savepoint name to rollback transaction to * @param integer $tupleCount Number of tuples found after insertion and rollback - * - * @dataProvider dataTransactionRollback */ + #[DataProvider('dataTransactionRollback')] public function testTransactionRollback(?string $toSavepoint, int $tupleCount) { $this->loadExampleData(); diff --git a/Tests/Mysqli/MysqliExporterTest.php b/Tests/Mysqli/MysqliExporterTest.php index 429b41c84..9059f3fd5 100644 --- a/Tests/Mysqli/MysqliExporterTest.php +++ b/Tests/Mysqli/MysqliExporterTest.php @@ -10,6 +10,7 @@ use Joomla\Database\Mysqli\MysqliDriver; use Joomla\Database\Mysqli\MysqliExporter; use Joomla\Database\Mysqli\MysqliQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -120,11 +121,12 @@ function ($name, $as = null) { /** * Data provider for string casting test cases * - * @return \Generator + * @return array */ - public function dataCastingToString(): \Generator + public static function dataCastingToString(): array { - yield 'without structure or data' => [ + return [ + 'without structure or data' => [ false, false, << XML , - ]; + ], - yield 'with only structure' => [ + 'with only structure' => [ true, false, << XML , - ]; + ], - yield 'with only data' => [ + 'with only data' => [ false, true, << XML , - ]; + ], - yield 'with structure and data' => [ + 'with structure and data' => [ true, true, << XML , + ], ]; } @@ -209,9 +212,8 @@ public function dataCastingToString(): \Generator * @param boolean $withStructure True to export the structure, false to not. * @param boolean $withData True to export the data, false to not. * @param string $expectedXml Expected XML string. - * - * @dataProvider dataCastingToString */ + #[DataProvider('dataCastingToString')] public function testCastingToString(bool $withStructure, bool $withData, string $expectedXml) { $exporter = new MysqliExporter(); @@ -244,45 +246,46 @@ public function testCastingToString(bool $withStructure, bool $withData, string /** * Data provider for check test cases * - * @return \Generator + * @return array */ - public function dataCheck(): \Generator + public static function dataCheck(): array { - yield 'passes checks' => [ - $this->createMock(MysqliDriver::class), - '#__dbtest', - null, - ]; + return [ + 'passes checks' => [ + MysqliDriver::class, + '#__dbtest', + null, + ], - yield 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), - '#__dbtest', - 'Database connection wrong type.', - ]; + 'fails checks with incorrect database driver subclass' => [ + DatabaseInterface::class, + '#__dbtest', + 'Database connection wrong type.', + ], - yield 'fails checks with no database driver' => [ - null, - '#__dbtest', - 'Database connection wrong type.', - ]; + 'fails checks with no database driver' => [ + null, + '#__dbtest', + 'Database connection wrong type.', + ], - yield 'fails checks with no tables' => [ - $this->createMock(MysqliDriver::class), - null, - 'ERROR: No Tables Specified', + 'fails checks with no tables' => [ + MysqliDriver::class, + null, + 'ERROR: No Tables Specified', + ], ]; } /** * @testdox The exporter checks for errors * - * @param DatabaseInterface|null $db Database driver to set in the exporter. - * @param string[]|string|null $from Database tables to export from. - * @param string|null $exceptionMessage If an Exception should be thrown, the expected message - * - * @dataProvider dataCheck + * @param string|null $db Database driver to set in the exporter. + * @param string[]|string|null $from Database tables to export from. + * @param string|null $exceptionMessage If an Exception should be thrown, the expected message */ - public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessage) + #[DataProvider('dataCheck')] + public function testCheck(?string $db, $from, ?string $exceptionMessage) { if ($exceptionMessage) { $this->expectException(\RuntimeException::class); @@ -292,7 +295,7 @@ public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessa $exporter = new MysqliExporter(); if ($db) { - $exporter->setDbo($db); + $exporter->setDbo($this->createMock($db)); } if ($from) { diff --git a/Tests/Mysqli/MysqliImporterTest.php b/Tests/Mysqli/MysqliImporterTest.php index b5fb6c1d0..b60f457dc 100644 --- a/Tests/Mysqli/MysqliImporterTest.php +++ b/Tests/Mysqli/MysqliImporterTest.php @@ -10,6 +10,7 @@ use Joomla\Database\Mysqli\MysqliDriver; use Joomla\Database\Mysqli\MysqliImporter; use Joomla\Database\Mysqli\MysqliQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -205,9 +206,9 @@ protected function tearDown(): void /** * Data provider for import test cases * - * @return \Generator + * @return array */ - public function dataImport(): \Generator + public static function dataImport(): array { $idField = ''; $titleField = ''; @@ -216,87 +217,89 @@ public function dataImport(): \Generator $idKey = ''; $titleKey = ''; - yield 'no changes in existing structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . ''), - [], - [], - ]; + return [ + 'no changes in existing structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . ''), + [], + [], + ], - yield 'inserts row into database' => [ - true, - true, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . ' 1Testing'), - [], - [ - 'jos_dbtest' => [ - (object) [ - 'id' => '1', - 'title' => 'Testing', + 'inserts row into database' => [ + true, + true, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . ' 1Testing'), + [], + [ + 'jos_dbtest' => [ + (object) [ + 'id' => '1', + 'title' => 'Testing', + ], ], ], ], - ]; - yield 'adds alias column to the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $aliasField . $idKey . ''), - [ - "ALTER TABLE `jos_dbtest` ADD COLUMN `alias` varchar(255) NOT NULL DEFAULT ''", + 'adds alias column to the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $aliasField . $idKey . ''), + [ + "ALTER TABLE `jos_dbtest` ADD COLUMN `alias` varchar(255) NOT NULL DEFAULT ''", + ], + [], ], - [], - ]; - yield 'adds key for the title column to the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . $titleKey . ''), - [ - 'ALTER TABLE `jos_dbtest` ADD UNIQUE KEY `idx_title` (`title`)', + 'adds key for the title column to the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . $titleKey . ''), + [ + 'ALTER TABLE `jos_dbtest` ADD UNIQUE KEY `idx_title` (`title`)', + ], + [], ], - [], - ]; - yield 'removes the title column from the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $idKey . ''), - [ - 'ALTER TABLE `jos_dbtest` DROP COLUMN `title`', + 'removes the title column from the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $idKey . ''), + [ + 'ALTER TABLE `jos_dbtest` DROP COLUMN `title`', + ], + [], ], - [], - ]; - yield 'removes the primary key based on the id column from the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . ''), - [ - 'ALTER TABLE `jos_dbtest` DROP PRIMARY KEY', + 'removes the primary key based on the id column from the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . ''), + [ + 'ALTER TABLE `jos_dbtest` DROP PRIMARY KEY', + ], + [], ], - [], - ]; - yield 'adds a new database table' => [ - true, - false, - new \SimpleXMLElement('' . $idField . $titleField . $idKey . '' . $idField . $titleField . $idKey . ''), - [ - "CREATE TABLE `#__newtest` (`id` int(11) unsigned NOT NULL DEFAULT '' AUTO_INCREMENT, `title` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`))", + 'adds a new database table' => [ + true, + false, + new \SimpleXMLElement('' . $idField . $titleField . $idKey . '' . $idField . $titleField . $idKey . ''), + [ + "CREATE TABLE `#__newtest` (`id` int(11) unsigned NOT NULL DEFAULT '' AUTO_INCREMENT, `title` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`))", + ], + [], ], - [], - ]; - yield 'changes the field type of the id field' => [ - true, - false, - new \SimpleXMLElement('' . $titleField . $idKey . ''), - [ - "ALTER TABLE `jos_dbtest` CHANGE COLUMN `id` `id` bigint() unsigned NOT NULL DEFAULT '' AUTO_INCREMENT", + 'changes the field type of the id field' => [ + true, + false, + new \SimpleXMLElement('' . $titleField . $idKey . ''), + [ + "ALTER TABLE `jos_dbtest` CHANGE COLUMN `id` `id` bigint() unsigned NOT NULL DEFAULT '' AUTO_INCREMENT", + ], + [], ], - [], ]; } @@ -308,9 +311,8 @@ public function dataImport(): \Generator * @param \SimpleXMLElement $from XML document to import. * @param string[] $expectedQueries The expected database queries to perform. * @param string[] $expectedInsertObjects The expected objects to be given to the database's insertObject method. - * - * @dataProvider dataImport */ + #[DataProvider('dataImport')] public function testImport(bool $mergeStructure, bool $importData, \SimpleXMLElement $from, array $expectedQueries, array $expectedInsertObjects) { $importer = new MysqliImporter(); @@ -332,45 +334,46 @@ public function testImport(bool $mergeStructure, bool $importData, \SimpleXMLEle /** * Data provider for check test cases * - * @return \Generator + * @return array */ - public function dataCheck(): \Generator + public static function dataCheck(): array { - yield 'passes checks' => [ - $this->createMock(MysqliDriver::class), - '#__dbtest', - null, - ]; + return [ + 'passes checks' => [ + MysqliDriver::class, + '#__dbtest', + null, + ], - yield 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), - new \SimpleXMLElement(''), - 'Database connection wrong type.', - ]; + 'fails checks with incorrect database driver subclass' => [ + DatabaseInterface::class, + new \SimpleXMLElement(''), + 'Database connection wrong type.', + ], - yield 'fails checks with no database driver' => [ - null, - new \SimpleXMLElement(''), - 'Database connection wrong type.', - ]; + 'fails checks with no database driver' => [ + null, + new \SimpleXMLElement(''), + 'Database connection wrong type.', + ], - yield 'fails checks with no tables' => [ - $this->createMock(MysqliDriver::class), - null, - 'ERROR: No Tables Specified', + 'fails checks with no tables' => [ + MysqliDriver::class, + null, + 'ERROR: No Tables Specified', + ], ]; } /** * @testdox The importer checks for errors * - * @param DatabaseInterface|null $db Database driver to set in the importer. - * @param string[]|string|null $from Database structure to import. - * @param string|null $exceptionMessage If an Exception should be thrown, the expected message - * - * @dataProvider dataCheck + * @param string|null $db Database driver to set in the importer. + * @param string[]|string|null $from Database structure to import. + * @param string|null $exceptionMessage If an Exception should be thrown, the expected message */ - public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessage) + #[DataProvider('dataCheck')] + public function testCheck(?string $db, $from, ?string $exceptionMessage) { if ($exceptionMessage) { $this->expectException(\RuntimeException::class); @@ -380,7 +383,7 @@ public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessa $importer = new MysqliImporter(); if ($db) { - $importer->setDbo($db); + $importer->setDbo($this->createMock($db)); } if ($from) { diff --git a/Tests/Mysqli/MysqliQueryTest.php b/Tests/Mysqli/MysqliQueryTest.php index d14e795c2..6b5893fe2 100644 --- a/Tests/Mysqli/MysqliQueryTest.php +++ b/Tests/Mysqli/MysqliQueryTest.php @@ -8,6 +8,7 @@ use Joomla\Database\DatabaseInterface; use Joomla\Database\Mysqli\MysqliQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -48,12 +49,14 @@ protected function setUp(): void /** * Data provider for concatenate test cases * - * @return \Generator + * @return array */ - public function dataConcatenate(): \Generator + public static function dataConcatenate(): array { - yield 'values without separator' => [['foo', 'bar'], null, 'CONCAT(foo,bar)']; - yield 'values with separator' => [['foo', 'bar'], ' and ', "CONCAT_WS(' and ', foo, bar)"]; + return [ + 'values without separator' => [['foo', 'bar'], null, 'CONCAT(foo,bar)'], + 'values with separator' => [['foo', 'bar'], ' and ', "CONCAT_WS(' and ', foo, bar)"], + ]; } /** @@ -62,9 +65,8 @@ public function dataConcatenate(): \Generator * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * @param string $expected The expected query string. - * - * @dataProvider dataConcatenate */ + #[DataProvider('dataConcatenate')] public function testConcatenate(array $values, ?string $separator, string $expected) { $this->db->expects($this->any()) diff --git a/Tests/Pgsql/PgsqlDriverTest.php b/Tests/Pgsql/PgsqlDriverTest.php index 93748813a..ee297a713 100644 --- a/Tests/Pgsql/PgsqlDriverTest.php +++ b/Tests/Pgsql/PgsqlDriverTest.php @@ -12,6 +12,7 @@ use Joomla\Database\Pgsql\PgsqlImporter; use Joomla\Database\Pgsql\PgsqlQuery; use Joomla\Database\Tests\AbstractDatabaseDriverTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * Test class for Joomla\Database\Pgsql\PgsqlDriver @@ -54,75 +55,77 @@ protected function tearDown(): void /** * Data provider for fetching table column test cases * - * @return \Generator + * @return array */ - public function dataGetTableColumns(): \Generator + public static function dataGetTableColumns(): array { - yield 'only column types' => [ - '#__dbtest', - true, - [ - 'id' => 'integer', - 'title' => 'character varying', - 'start_date' => 'timestamp without time zone', - 'description' => 'text', - 'data' => 'bytea', + return [ + 'only column types' => [ + '#__dbtest', + true, + [ + 'id' => 'integer', + 'title' => 'character varying', + 'start_date' => 'timestamp without time zone', + 'description' => 'text', + 'data' => 'bytea', + ], ], - ]; - yield 'full column information' => [ - '#__dbtest', - false, - [ - 'id' => (object) [ - 'column_name' => 'id', - 'Field' => 'id', - 'type' => 'integer', - 'Type' => 'integer', - 'null' => 'NO', - 'Null' => 'NO', - 'Default' => 'nextval(\'dbtest_id_seq\'::regclass)', - 'comments' => '', - ], - 'title' => (object) [ - 'column_name' => 'title', - 'Field' => 'title', - 'type' => 'character varying(50)', - 'Type' => 'character varying(50)', - 'null' => 'NO', - 'Null' => 'NO', - 'Default' => null, - 'comments' => '', - ], - 'start_date' => (object) [ - 'column_name' => 'start_date', - 'Field' => 'start_date', - 'type' => 'timestamp without time zone', - 'Type' => 'timestamp without time zone', - 'null' => 'NO', - 'Null' => 'NO', - 'Default' => null, - 'comments' => '', - ], - 'description' => (object) [ - 'column_name' => 'description', - 'Field' => 'description', - 'type' => 'text', - 'Type' => 'text', - 'null' => 'NO', - 'Null' => 'NO', - 'Default' => null, - 'comments' => '', - ], - 'data' => (object) [ - 'column_name' => 'data', - 'Field' => 'data', - 'type' => 'bytea', - 'Type' => 'bytea', - 'null' => 'YES', - 'Null' => 'YES', - 'Default' => null, - 'comments' => '', + 'full column information' => [ + '#__dbtest', + false, + [ + 'id' => (object) [ + 'column_name' => 'id', + 'Field' => 'id', + 'type' => 'integer', + 'Type' => 'integer', + 'null' => 'NO', + 'Null' => 'NO', + 'Default' => 'nextval(\'dbtest_id_seq\'::regclass)', + 'comments' => '', + ], + 'title' => (object) [ + 'column_name' => 'title', + 'Field' => 'title', + 'type' => 'character varying(50)', + 'Type' => 'character varying(50)', + 'null' => 'NO', + 'Null' => 'NO', + 'Default' => null, + 'comments' => '', + ], + 'start_date' => (object) [ + 'column_name' => 'start_date', + 'Field' => 'start_date', + 'type' => 'timestamp without time zone', + 'Type' => 'timestamp without time zone', + 'null' => 'NO', + 'Null' => 'NO', + 'Default' => null, + 'comments' => '', + ], + 'description' => (object) [ + 'column_name' => 'description', + 'Field' => 'description', + 'type' => 'text', + 'Type' => 'text', + 'null' => 'NO', + 'Null' => 'NO', + 'Default' => null, + 'comments' => '', + ], + 'data' => (object) [ + 'column_name' => 'data', + 'Field' => 'data', + 'type' => 'bytea', + 'Type' => 'bytea', + 'null' => 'YES', + 'Null' => 'YES', + 'Default' => null, + 'comments' => '', + ], ], ], ]; @@ -131,50 +134,58 @@ public function dataGetTableColumns(): \Generator /** * Data provider for binary quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteBinary(): \Generator + public static function dataQuoteBinary(): array { - yield ['DATA', "decode('44415441', 'hex')"]; - yield ["\x00\x01\x02\xff", "decode('000102ff', 'hex')"]; - yield ["\x01\x01\x02\xff", "decode('010102ff', 'hex')"]; + return [ + ['DATA', "decode('44415441', 'hex')"], + ["\x00\x01\x02\xff", "decode('000102ff', 'hex')"], + ["\x01\x01\x02\xff", "decode('010102ff', 'hex')"], + ]; } /** * Data provider for table dropping test cases * - * @return \Generator + * @return array */ - public function dataDropTable() + public static function dataDropTable(): array { - yield 'database does not exist before query' => ['#__foo', false]; + return [ + 'database does not exist before query' => ['#__foo', false], + ]; } /** * Data provider for escaping test cases * - * @return \Generator + * @return array */ - public function dataEscape(): \Generator + public static function dataEscape(): array { - yield ["'%_abc123", false, '\'\'%_abc123']; - yield ["'%_abc123", true, '\'\'%_abc123']; - yield ["\'%_abc123", false, '\\\\\'\'%_abc123']; - yield ["\'%_abc123", true, '\\\\\'\'%_abc123']; - yield [3, false, 3]; - yield [3.14, false, '3.14']; + return [ + ["'%_abc123", false, '\'\'%_abc123'], + ["'%_abc123", true, '\'\'%_abc123'], + ["\'%_abc123", false, '\\\\\'\'%_abc123'], + ["\'%_abc123", true, '\\\\\'\'%_abc123'], + [3, false, 3], + [3.14, false, '3.14'], + ]; } /** * Data provider for name quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteName(): \Generator + public static function dataQuoteName(): array { - yield ['protected`title', null, '"protected`title"']; - yield ['protected"title', null, '"protected""title"']; - yield ['protected]title', null, '"protected]title"']; + return [ + ['protected`title', null, '"protected`title"'], + ['protected"title', null, '"protected""title"'], + ['protected]title', null, '"protected]title"'], + ]; } /* @@ -452,13 +463,15 @@ public function testTransactionCommit() /** * Data provider for transaction rollback test cases * - * @return \Generator + * @return array */ - public function dataTransactionRollback() + public static function dataTransactionRollback(): array { - yield 'rollback without savepoint' => [null, 0]; + return [ + 'rollback without savepoint' => [null, 0], - yield 'rollback with savepoint' => ['transactionSavepoint', 1]; + 'rollback with savepoint' => ['transactionSavepoint', 1], + ]; } /** @@ -466,9 +479,8 @@ public function dataTransactionRollback() * * @param string|null $toSavepoint Savepoint name to rollback transaction to * @param integer $tupleCount Number of tuples found after insertion and rollback - * - * @dataProvider dataTransactionRollback */ + #[DataProvider('dataTransactionRollback')] public function testTransactionRollback(?string $toSavepoint, int $tupleCount) { $this->loadExampleData(); diff --git a/Tests/Pgsql/PgsqlExporterTest.php b/Tests/Pgsql/PgsqlExporterTest.php index ad83cc38b..468b72691 100644 --- a/Tests/Pgsql/PgsqlExporterTest.php +++ b/Tests/Pgsql/PgsqlExporterTest.php @@ -10,6 +10,7 @@ use Joomla\Database\Pgsql\PgsqlDriver; use Joomla\Database\Pgsql\PgsqlExporter; use Joomla\Database\Pgsql\PgsqlQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -142,11 +143,12 @@ function ($name, $as = null) { /** * Data provider for string casting test cases * - * @return \Generator + * @return array */ - public function dataCastingToString(): \Generator + public static function dataCastingToString(): array { - yield 'without structure or data' => [ + return [ + 'without structure or data' => [ false, false, << XML , - ]; + ], - yield 'with only structure' => [ + 'with only structure' => [ true, false, << XML , - ]; + ], - yield 'with only data' => [ + 'with only data' => [ false, true, << XML , - ]; + ], - yield 'with structure and data' => [ + 'with structure and data' => [ true, true, << XML , + ], ]; } @@ -237,9 +240,8 @@ public function dataCastingToString(): \Generator * @param boolean $withStructure True to export the structure, false to not. * @param boolean $withData True to export the data, false to not. * @param string $expectedXml Expected XML string. - * - * @dataProvider dataCastingToString */ + #[DataProvider('dataCastingToString')] public function testCastingToString(bool $withStructure, bool $withData, string $expectedXml) { $exporter = new PgsqlExporter(); @@ -272,45 +274,46 @@ public function testCastingToString(bool $withStructure, bool $withData, string /** * Data provider for check test cases * - * @return \Generator + * @return array */ - public function dataCheck(): \Generator + public static function dataCheck(): array { - yield 'passes checks' => [ - $this->createMock(PgsqlDriver::class), - '#__dbtest', - null, - ]; + return [ + 'passes checks' => [ + PgsqlDriver::class, + '#__dbtest', + null, + ], - yield 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), - '#__dbtest', - 'Database connection wrong type.', - ]; + 'fails checks with incorrect database driver subclass' => [ + DatabaseInterface::class, + '#__dbtest', + 'Database connection wrong type.', + ], - yield 'fails checks with no database driver' => [ - null, - '#__dbtest', - 'Database connection wrong type.', - ]; + 'fails checks with no database driver' => [ + null, + '#__dbtest', + 'Database connection wrong type.', + ], - yield 'fails checks with no tables' => [ - $this->createMock(PgsqlDriver::class), - null, - 'ERROR: No Tables Specified', + 'fails checks with no tables' => [ + PgsqlDriver::class, + null, + 'ERROR: No Tables Specified', + ], ]; } /** * @testdox The exporter checks for errors * - * @param DatabaseInterface|null $db Database driver to set in the exporter. - * @param string[]|string|null $from Database tables to export from. - * @param string|null $exceptionMessage If an Exception should be thrown, the expected message - * - * @dataProvider dataCheck + * @param string|null $db Database driver to set in the exporter. + * @param string[]|string|null $from Database tables to export from. + * @param string|null $exceptionMessage If an Exception should be thrown, the expected message */ - public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessage) + #[DataProvider('dataCheck')] + public function testCheck(?string $db, $from, ?string $exceptionMessage) { if ($exceptionMessage) { $this->expectException(\RuntimeException::class); @@ -320,7 +323,7 @@ public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessa $exporter = new PgsqlExporter(); if ($db) { - $exporter->setDbo($db); + $exporter->setDbo($this->createMock($db)); } if ($from) { diff --git a/Tests/Pgsql/PgsqlImporterTest.php b/Tests/Pgsql/PgsqlImporterTest.php index 92750c88a..ff87b852f 100644 --- a/Tests/Pgsql/PgsqlImporterTest.php +++ b/Tests/Pgsql/PgsqlImporterTest.php @@ -10,6 +10,7 @@ use Joomla\Database\Pgsql\PgsqlDriver; use Joomla\Database\Pgsql\PgsqlImporter; use Joomla\Database\Pgsql\PgsqlQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -211,9 +212,9 @@ protected function tearDown(): void /** * Data provider for import test cases * - * @return \Generator + * @return array */ - public function dataImport(): \Generator + public static function dataImport(): array { $idSequence = ''; @@ -224,93 +225,95 @@ public function dataImport(): \Generator $idKey = ''; $titleKey = ''; - yield 'no changes in existing structure' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . ''), - [], - [], - ]; + return [ + 'no changes in existing structure' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . ''), + [], + [], + ], - yield 'inserts row into database' => [ - true, - true, - new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . ' 1Testing'), - [], - [ - 'jos_dbtest' => [ - (object) [ - 'id' => '1', - 'title' => 'Testing', + 'inserts row into database' => [ + true, + true, + new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . ' 1Testing'), + [], + [ + 'jos_dbtest' => [ + (object) [ + 'id' => '1', + 'title' => 'Testing', + ], ], ], ], - ]; - yield 'adds alias column to the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $aliasField . $idKey . ''), - [ - "ALTER TABLE \"jos_dbtest\" ADD COLUMN \"alias\" character varying(255) NOT NULL DEFAULT 'test'", + 'adds alias column to the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $aliasField . $idKey . ''), + [ + "ALTER TABLE \"jos_dbtest\" ADD COLUMN \"alias\" character varying(255) NOT NULL DEFAULT 'test'", + ], + [], ], - [], - ]; - yield 'adds key for the title column to the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . $titleKey . ''), - [ - 'CREATE INDEX jos_dbtest_idx_name ON jos_dbtest USING btree (name)', + 'adds key for the title column to the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . $titleKey . ''), + [ + 'CREATE INDEX jos_dbtest_idx_name ON jos_dbtest USING btree (name)', + ], + [], ], - [], - ]; - yield 'removes the title column from the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . $idField . $idKey . ''), - [ - 'ALTER TABLE "jos_dbtest" DROP COLUMN "title"', + 'removes the title column from the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . $idField . $idKey . ''), + [ + 'ALTER TABLE "jos_dbtest" DROP COLUMN "title"', + ], + [], ], - [], - ]; - yield 'removes the primary key based on the id column from the structure' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . $idField . $titleField . ''), - [ - 'ALTER TABLE ONLY "jos_dbtest" DROP CONSTRAINT "jos_dbtest_pkey"', + 'removes the primary key based on the id column from the structure' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . $idField . $titleField . ''), + [ + 'ALTER TABLE ONLY "jos_dbtest" DROP CONSTRAINT "jos_dbtest_pkey"', + ], + [], ], - [], - ]; - yield 'adds a new database table' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . '' . $titleField . ''), - [ - 'CREATE TABLE "jos_newtest" ("id" SERIAL, "title" character varying(50) NOT NULL DEFAULT \'NULL\')', - 'CREATE SEQUENCE IF NOT EXISTS jos_newtest_id_seq INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 NO CYCLE OWNED BY "public.jos_newtest.id"', - "SELECT setval('jos_newtest_id_seq', , FALSE)", - 'ALTER TABLE jos_newtest ADD PRIMARY KEY (id)', + 'adds a new database table' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . $idField . $titleField . $idKey . '' . $titleField . ''), + [ + 'CREATE TABLE "jos_newtest" ("id" SERIAL, "title" character varying(50) NOT NULL DEFAULT \'NULL\')', + 'CREATE SEQUENCE IF NOT EXISTS jos_newtest_id_seq INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 NO CYCLE OWNED BY "public.jos_newtest.id"', + "SELECT setval('jos_newtest_id_seq', , FALSE)", + 'ALTER TABLE jos_newtest ADD PRIMARY KEY (id)', + ], + [], ], - [], - ]; - yield 'changes the field type of the id field' => [ - true, - false, - new \SimpleXMLElement('' . $idSequence . '' . $titleField . $idKey . ''), - [ - 'ALTER TABLE "jos_dbtest" ALTER COLUMN "id" TYPE bigint, -ALTER COLUMN "id" SET NOT NULL, -ALTER COLUMN "id" SET DEFAULT \'nextval(\'jos_dbtest_id_seq\'::regclass)\'; -ALTER SEQUENCE "jos_dbtest_id_seq" OWNED BY "jos_dbtest.id"', + 'changes the field type of the id field' => [ + true, + false, + new \SimpleXMLElement('' . $idSequence . '' . $titleField . $idKey . ''), + [ + 'ALTER TABLE "jos_dbtest" ALTER COLUMN "id" TYPE bigint, + ALTER COLUMN "id" SET NOT NULL, + ALTER COLUMN "id" SET DEFAULT \'nextval(\'jos_dbtest_id_seq\'::regclass)\'; + ALTER SEQUENCE "jos_dbtest_id_seq" OWNED BY "jos_dbtest.id"', + ], + [], ], - [], ]; } @@ -322,9 +325,8 @@ public function dataImport(): \Generator * @param \SimpleXMLElement $from XML document to import. * @param string[] $expectedQueries The expected database queries to perform. * @param string[] $expectedInsertObjects The expected objects to be given to the database's insertObject method. - * - * @dataProvider dataImport */ + #[DataProvider('dataImport')] public function testImport(bool $mergeStructure, bool $importData, \SimpleXMLElement $from, array $expectedQueries, array $expectedInsertObjects) { $importer = new PgsqlImporter(); @@ -346,45 +348,46 @@ public function testImport(bool $mergeStructure, bool $importData, \SimpleXMLEle /** * Data provider for check test cases * - * @return \Generator + * @return array */ - public function dataCheck(): \Generator + public static function dataCheck(): array { - yield 'passes checks' => [ - $this->createMock(PgsqlDriver::class), - '#__dbtest', - null, - ]; + return [ + 'passes checks' => [ + $this->createMock(PgsqlDriver::class), + '#__dbtest', + null, + ], - yield 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), - new \SimpleXMLElement(''), - 'Database connection wrong type.', - ]; + 'fails checks with incorrect database driver subclass' => [ + $this->createMock(DatabaseInterface::class), + new \SimpleXMLElement(''), + 'Database connection wrong type.', + ], - yield 'fails checks with no database driver' => [ - null, - new \SimpleXMLElement(''), - 'Database connection wrong type.', - ]; + 'fails checks with no database driver' => [ + null, + new \SimpleXMLElement(''), + 'Database connection wrong type.', + ], - yield 'fails checks with no tables' => [ - $this->createMock(PgsqlDriver::class), - null, - 'ERROR: No Tables Specified', + 'fails checks with no tables' => [ + $this->createMock(PgsqlDriver::class), + null, + 'ERROR: No Tables Specified', + ], ]; } /** * @testdox The importer checks for errors * - * @param DatabaseInterface|null $db Database driver to set in the importer. - * @param string[]|string|null $from Database structure to import. - * @param string|null $exceptionMessage If an Exception should be thrown, the expected message - * - * @dataProvider dataCheck + * @param string|null $db Database driver to set in the importer. + * @param string[]|string|null $from Database structure to import. + * @param string|null $exceptionMessage If an Exception should be thrown, the expected message */ - public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessage) + #[DataProvider('dataCheck')] + public function testCheck(?string $db, $from, ?string $exceptionMessage) { if ($exceptionMessage) { $this->expectException(\RuntimeException::class); @@ -394,7 +397,7 @@ public function testCheck(?DatabaseInterface $db, $from, ?string $exceptionMessa $importer = new PgsqlImporter(); if ($db) { - $importer->setDbo($db); + $importer->setDbo($this->createMock($db)); } if ($from) { diff --git a/Tests/Pgsql/PgsqlQueryTest.php b/Tests/Pgsql/PgsqlQueryTest.php index a32a4eb01..b04f63a6b 100644 --- a/Tests/Pgsql/PgsqlQueryTest.php +++ b/Tests/Pgsql/PgsqlQueryTest.php @@ -8,6 +8,7 @@ use Joomla\Database\DatabaseInterface; use Joomla\Database\Pgsql\PgsqlQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -78,12 +79,14 @@ public function testCastAsWithIntegerType() /** * Data provider for concatenate test cases * - * @return \Generator + * @return array */ - public function dataConcatenate(): \Generator + public static function dataConcatenate(): array { - yield 'values without separator' => [['foo', 'bar'], null, 'foo || bar']; - yield 'values with separator' => [['foo', 'bar'], ' and ', "foo || ' and ' || bar"]; + return [ + 'values without separator' => [['foo', 'bar'], null, 'foo || bar'], + 'values with separator' => [['foo', 'bar'], ' and ', "foo || ' and ' || bar"], + ]; } /** @@ -92,9 +95,8 @@ public function dataConcatenate(): \Generator * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * @param string $expected The expected query string. - * - * @dataProvider dataConcatenate */ + #[DataProvider('dataConcatenate')] public function testConcatenate(array $values, ?string $separator, string $expected) { $this->db->expects($this->any()) @@ -217,12 +219,14 @@ public function testSecond() /** * Data provider for dateAdd test cases * - * @return \Generator + * @return array */ - public function dataDateAdd(): \Generator + public static function dataDateAdd(): array { - yield 'date with positive interval' => ["'2019-10-13'", '1', 'DAY', "timestamp '2019-10-13' + interval '1 DAY'"]; - yield 'date with negative interval' => ["'2019-10-13'", '-1', 'DAY', "timestamp '2019-10-13' - interval '1 DAY'"]; + return [ + 'date with positive interval' => ["'2019-10-13'", '1', 'DAY', "timestamp '2019-10-13' + interval '1 DAY'"], + 'date with negative interval' => ["'2019-10-13'", '-1', 'DAY', "timestamp '2019-10-13' - interval '1 DAY'"], + ]; } /** @@ -232,9 +236,8 @@ public function dataDateAdd(): \Generator * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * @param string $expected The expected query string. - * - * @dataProvider dataDateAdd */ + #[DataProvider('dataDateAdd')] public function testDateAdd(string $date, string $interval, string $datePart, string $expected) { $this->assertSame( diff --git a/Tests/Sqlite/SqliteDriverTest.php b/Tests/Sqlite/SqliteDriverTest.php index 940198c5e..3d1ec2fc7 100644 --- a/Tests/Sqlite/SqliteDriverTest.php +++ b/Tests/Sqlite/SqliteDriverTest.php @@ -13,6 +13,7 @@ use Joomla\Database\Sqlite\SqliteDriver; use Joomla\Database\Sqlite\SqliteQuery; use Joomla\Database\Tests\AbstractDatabaseDriverTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * Test class for Joomla\Database\Sqlite\SqliteDriver @@ -88,73 +89,77 @@ function (string $table): bool { /** * Data provider for escaping test cases * - * @return \Generator + * @return array */ - public function dataEscape(): \Generator + public static function dataEscape(): array { - yield ["'%_abc123", false, "''%_abc123"]; - yield ["'%_abc123", true, "''%_abc123"]; - yield [3, false, 3]; - yield [3.14, false, '3.14']; + return [ + ["'%_abc123", false, "''%_abc123"], + ["'%_abc123", true, "''%_abc123"], + [3, false, 3], + [3.14, false, '3.14'], + ]; } /** * Data provider for fetching table column test cases * - * @return \Generator - */ - public function dataGetTableColumns(): \Generator - { - yield 'only column types' => [ - '#__dbtest', - true, - [ - 'id' => 'INTEGER', - 'title' => 'TEXT', - 'start_date' => 'TEXT', - 'description' => 'TEXT', - 'data' => 'BLOB', + * @return array + */ + public static function dataGetTableColumns(): array + { + return [ + 'only column types' => [ + '#__dbtest', + true, + [ + 'id' => 'INTEGER', + 'title' => 'TEXT', + 'start_date' => 'TEXT', + 'description' => 'TEXT', + 'data' => 'BLOB', + ], ], - ]; - yield 'full column information' => [ - '#__dbtest', - false, - [ - 'id' => (object) [ - 'Field' => 'id', - 'Type' => 'INTEGER', - 'Null' => 'YES', - 'Default' => null, - 'Key' => 'PRI', - ], - 'title' => (object) [ - 'Field' => 'title', - 'Type' => 'TEXT', - 'Null' => 'NO', - 'Default' => '\'\'', - 'Key' => '', - ], - 'start_date' => (object) [ - 'Field' => 'start_date', - 'Type' => 'TEXT', - 'Null' => 'NO', - 'Default' => '\'\'', - 'Key' => '', - ], - 'description' => (object) [ - 'Field' => 'description', - 'Type' => 'TEXT', - 'Null' => 'NO', - 'Default' => '\'\'', - 'Key' => '', - ], - 'data' => (object) [ - 'Field' => 'data', - 'Type' => 'BLOB', - 'Null' => 'YES', - 'Default' => null, - 'Key' => '', + 'full column information' => [ + '#__dbtest', + false, + [ + 'id' => (object) [ + 'Field' => 'id', + 'Type' => 'INTEGER', + 'Null' => 'YES', + 'Default' => null, + 'Key' => 'PRI', + ], + 'title' => (object) [ + 'Field' => 'title', + 'Type' => 'TEXT', + 'Null' => 'NO', + 'Default' => '\'\'', + 'Key' => '', + ], + 'start_date' => (object) [ + 'Field' => 'start_date', + 'Type' => 'TEXT', + 'Null' => 'NO', + 'Default' => '\'\'', + 'Key' => '', + ], + 'description' => (object) [ + 'Field' => 'description', + 'Type' => 'TEXT', + 'Null' => 'NO', + 'Default' => '\'\'', + 'Key' => '', + ], + 'data' => (object) [ + 'Field' => 'data', + 'Type' => 'BLOB', + 'Null' => 'YES', + 'Default' => null, + 'Key' => '', + ], ], ], ]; @@ -163,37 +168,43 @@ public function dataGetTableColumns(): \Generator /** * Data provider for table dropping test cases * - * @return \Generator + * @return array */ - public function dataDropTable() + public static function dataDropTable(): array { - yield 'database exists before query' => ['#__dbtest', true]; + return [ + 'database exists before query' => ['#__dbtest', true], - yield 'database does not exist before query' => ['#__foo', false]; + 'database does not exist before query' => ['#__foo', false], + ]; } /** * Data provider for binary quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteBinary(): \Generator + public static function dataQuoteBinary(): array { - yield ['DATA', "X'" . bin2hex('DATA') . "'"]; - yield ["\x00\x01\x02\xff", "X'000102ff'"]; - yield ["\x01\x01\x02\xff", "X'010102ff'"]; + return [ + ['DATA', "X'" . bin2hex('DATA') . "'"], + ["\x00\x01\x02\xff", "X'000102ff'"], + ["\x01\x01\x02\xff", "X'010102ff'"], + ]; } /** * Data provider for name quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteName(): \Generator + public static function dataQuoteName(): array { - yield ['protected`title', null, '`protected``title`']; - yield ['protected"title', null, '`protected"title`']; - yield ['protected]title', null, '`protected]title`']; + return [ + ['protected`title', null, '`protected``title`'], + ['protected"title', null, '`protected"title`'], + ['protected]title', null, '`protected]title`'], + ]; } /* @@ -412,13 +423,15 @@ public function testTransactionCommit() /** * Data provider for transaction rollback test cases * - * @return \Generator + * @return array */ - public function dataTransactionRollback() + public static function dataTransactionRollback() { - yield 'rollback without savepoint' => [null, 0]; + return [ + 'rollback without savepoint' => [null, 0], - yield 'rollback with savepoint' => ['transactionSavepoint', 1]; + 'rollback with savepoint' => ['transactionSavepoint', 1], + ]; } /** @@ -426,9 +439,8 @@ public function dataTransactionRollback() * * @param string|null $toSavepoint Savepoint name to rollback transaction to * @param integer $tupleCount Number of tuples found after insertion and rollback - * - * @dataProvider dataTransactionRollback */ + #[DataProvider('dataTransactionRollback')] public function testTransactionRollback(?string $toSavepoint, int $tupleCount) { $this->loadExampleData(); diff --git a/Tests/Sqlite/SqliteQueryTest.php b/Tests/Sqlite/SqliteQueryTest.php index fdb910c43..5a444d40c 100644 --- a/Tests/Sqlite/SqliteQueryTest.php +++ b/Tests/Sqlite/SqliteQueryTest.php @@ -8,6 +8,7 @@ use Joomla\Database\DatabaseInterface; use Joomla\Database\Sqlite\SqliteQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -48,12 +49,14 @@ protected function setUp(): void /** * Data provider for character length test cases * - * @return \Generator + * @return array */ - public function dataCharLength(): \Generator + public static function dataCharLength(): array { - yield 'field without comparison' => ['a.title', null, null, 'length(a.title)']; - yield 'field with comparison' => ['a.title', '!=', '0', 'length(a.title) != 0']; + return [ + 'field without comparison' => ['a.title', null, null, 'length(a.title)'], + 'field with comparison' => ['a.title', '!=', '0', 'length(a.title) != 0'], + ]; } /** @@ -63,9 +66,8 @@ public function dataCharLength(): \Generator * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * @param string $expected The expected query string. - * - * @dataProvider dataCharLength */ + #[DataProvider('dataCharLength')] public function testCharLength(string $field, ?string $operator, ?string $condition, string $expected) { $this->assertSame( @@ -77,12 +79,14 @@ public function testCharLength(string $field, ?string $operator, ?string $condit /** * Data provider for concatenate test cases * - * @return \Generator + * @return array */ - public function dataConcatenate(): \Generator + public static function dataConcatenate(): array { - yield 'values without separator' => [['foo', 'bar'], null, 'foo || bar']; - yield 'values with separator' => [['foo', 'bar'], ' and ', "foo || ' and ' || bar"]; + return [ + 'values without separator' => [['foo', 'bar'], null, 'foo || bar'], + 'values with separator' => [['foo', 'bar'], ' and ', "foo || ' and ' || bar"], + ]; } /** @@ -91,9 +95,8 @@ public function dataConcatenate(): \Generator * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * @param string $expected The expected query string. - * - * @dataProvider dataConcatenate */ + #[DataProvider('dataConcatenate')] public function testConcatenate(array $values, ?string $separator, string $expected) { $this->db->expects($this->any()) diff --git a/Tests/Sqlsrv/SqlsrvDriverTest.php b/Tests/Sqlsrv/SqlsrvDriverTest.php index 9f8d96071..19bda8fc7 100644 --- a/Tests/Sqlsrv/SqlsrvDriverTest.php +++ b/Tests/Sqlsrv/SqlsrvDriverTest.php @@ -12,6 +12,7 @@ use Joomla\Database\Sqlsrv\SqlsrvDriver; use Joomla\Database\Sqlsrv\SqlsrvQuery; use Joomla\Database\Tests\AbstractDatabaseDriverTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * Test class for Joomla\Database\Sqlsrv\SqlsrvDriver. @@ -121,68 +122,72 @@ protected function loadExampleData(): void /** * Data provider for escaping test cases * - * @return \Generator + * @return array */ - public function dataEscape(): \Generator + public static function dataEscape(): array { - yield ["'%_abc123", false, '\'\'%_abc123']; - yield ["'%_abc123", true, '\'\'[%][_]abc123']; - yield [3, false, 3]; - yield [3.14, false, '3.14']; + return [ + ["'%_abc123", false, '\'\'%_abc123'], + ["'%_abc123", true, '\'\'[%][_]abc123'], + [3, false, 3], + [3.14, false, '3.14'], + ]; } /** * Data provider for fetching table column test cases * - * @return \Generator + * @return array */ - public function dataGetTableColumns(): \Generator + public static function dataGetTableColumns(): array { - yield 'only column types' => [ - '#__dbtest', - true, - [ - 'id' => 'int', - 'title' => 'nvarchar', - 'start_date' => 'datetime', - 'description' => 'nvarchar', - 'data' => 'nvarchar', + return [ + 'only column types' => [ + '#__dbtest', + true, + [ + 'id' => 'int', + 'title' => 'nvarchar', + 'start_date' => 'datetime', + 'description' => 'nvarchar', + 'data' => 'nvarchar', + ], ], - ]; - yield 'full column information' => [ - '#__dbtest', - false, - [ - 'id' => (object) [ - 'Field' => 'id', - 'Type' => 'int', - 'Null' => 'NO', - 'Default' => '', - ], - 'title' => (object) [ - 'Field' => 'title', - 'Type' => 'nvarchar', - 'Null' => 'NO', - 'Default' => '', - ], - 'start_date' => (object) [ - 'Field' => 'start_date', - 'Type' => 'datetime', - 'Null' => 'NO', - 'Default' => '', - ], - 'description' => (object) [ - 'Field' => 'description', - 'Type' => 'nvarchar', - 'Null' => 'NO', - 'Default' => '', - ], - 'data' => (object) [ - 'Field' => 'data', - 'Type' => 'nvarchar', - 'Null' => 'YES', - 'Default' => '', + 'full column information' => [ + '#__dbtest', + false, + [ + 'id' => (object) [ + 'Field' => 'id', + 'Type' => 'int', + 'Null' => 'NO', + 'Default' => '', + ], + 'title' => (object) [ + 'Field' => 'title', + 'Type' => 'nvarchar', + 'Null' => 'NO', + 'Default' => '', + ], + 'start_date' => (object) [ + 'Field' => 'start_date', + 'Type' => 'datetime', + 'Null' => 'NO', + 'Default' => '', + ], + 'description' => (object) [ + 'Field' => 'description', + 'Type' => 'nvarchar', + 'Null' => 'NO', + 'Default' => '', + ], + 'data' => (object) [ + 'Field' => 'data', + 'Type' => 'nvarchar', + 'Null' => 'YES', + 'Default' => '', + ], ], ], ]; @@ -191,25 +196,29 @@ public function dataGetTableColumns(): \Generator /** * Data provider for binary quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteBinary(): \Generator + public static function dataQuoteBinary(): array { - yield ['DATA', "0x" . bin2hex('DATA')]; - yield ["\x00\x01\x02\xff", "0x000102ff"]; - yield ["\x01\x01\x02\xff", "0x010102ff"]; + return [ + ['DATA', "0x" . bin2hex('DATA')], + ["\x00\x01\x02\xff", "0x000102ff"], + ["\x01\x01\x02\xff", "0x010102ff"], + ]; } /** * Data provider for name quoting test cases * - * @return \Generator + * @return array */ - public function dataQuoteName(): \Generator + public static function dataQuoteName(): array { - yield ['protected`title', null, '[protected`title]']; - yield ['protected"title', null, '[protected"title]']; - yield ['protected]title', null, '[protected]]title]']; + return [ + ['protected`title', null, '[protected`title]'], + ['protected"title', null, '[protected"title]'], + ['protected]title', null, '[protected]]title]'], + ]; } /* @@ -469,9 +478,8 @@ public function testGetTableCreate() * @param string $table The name of the database table. * @param boolean $typeOnly True (default) to only return field types. * @param array $expected Expected result. - * - * @dataProvider dataGetTableColumns */ + #[DataProvider('dataGetTableColumns')] public function testGetTableColumns(string $table, bool $typeOnly, array $expected) { $this->assertEquals( diff --git a/Tests/Sqlsrv/SqlsrvQueryTest.php b/Tests/Sqlsrv/SqlsrvQueryTest.php index e7819d18d..cddd42838 100644 --- a/Tests/Sqlsrv/SqlsrvQueryTest.php +++ b/Tests/Sqlsrv/SqlsrvQueryTest.php @@ -8,6 +8,7 @@ use Joomla\Database\DatabaseInterface; use Joomla\Database\Sqlsrv\SqlsrvQuery; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -78,12 +79,14 @@ public function testCastAsWithIntegerType() /** * Data provider for character length test cases * - * @return \Generator + * @return array */ - public function dataCharLength(): \Generator + public static function dataCharLength(): array { - yield 'field without comparison' => ['a.title', null, null, 'DATALENGTH(a.title)']; - yield 'field with comparison' => ['a.title', '!=', '0', 'DATALENGTH(a.title) != 0']; + return [ + 'field without comparison' => ['a.title', null, null, 'DATALENGTH(a.title)'], + 'field with comparison' => ['a.title', '!=', '0', 'DATALENGTH(a.title) != 0'], + ]; } /** @@ -93,9 +96,8 @@ public function dataCharLength(): \Generator * @param string|null $operator Comparison operator between charLength integer value and $condition * @param string|null $condition Integer value to compare charLength with. * @param string $expected The expected query string. - * - * @dataProvider dataCharLength */ + #[DataProvider('dataCharLength')] public function testCharLength(string $field, ?string $operator, ?string $condition, string $expected) { $this->assertSame( @@ -107,12 +109,14 @@ public function testCharLength(string $field, ?string $operator, ?string $condit /** * Data provider for concatenate test cases * - * @return \Generator + * @return array */ - public function dataConcatenate(): \Generator + public static function dataConcatenate(): array { - yield 'values without separator' => [['foo', 'bar'], null, '(foo+bar)']; - yield 'values with separator' => [['foo', 'bar'], ' and ', "(foo+' and '+bar)"]; + return [ + 'values without separator' => [['foo', 'bar'], null, '(foo+bar)'], + 'values with separator' => [['foo', 'bar'], ' and ', "(foo+' and '+bar)"], + ]; } /** @@ -121,9 +125,8 @@ public function dataConcatenate(): \Generator * @param string[] $values An array of values to concatenate. * @param string|null $separator As separator to place between each value. * @param string $expected The expected query string. - * - * @dataProvider dataConcatenate */ + #[DataProvider('dataConcatenate')] public function testConcatenate(array $values, ?string $separator, string $expected) { $this->db->expects($this->any()) From 7f403c1fbf90dfea14433a4740ddbfd247580e88 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Sun, 13 Jul 2025 15:36:35 +0200 Subject: [PATCH 16/26] Updating unittests to phpunit 12 - part 3 --- Tests/Pgsql/PgsqlImporterTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/Pgsql/PgsqlImporterTest.php b/Tests/Pgsql/PgsqlImporterTest.php index ff87b852f..b25ba792e 100644 --- a/Tests/Pgsql/PgsqlImporterTest.php +++ b/Tests/Pgsql/PgsqlImporterTest.php @@ -308,9 +308,9 @@ public static function dataImport(): array new \SimpleXMLElement('' . $idSequence . '' . $titleField . $idKey . ''), [ 'ALTER TABLE "jos_dbtest" ALTER COLUMN "id" TYPE bigint, - ALTER COLUMN "id" SET NOT NULL, - ALTER COLUMN "id" SET DEFAULT \'nextval(\'jos_dbtest_id_seq\'::regclass)\'; - ALTER SEQUENCE "jos_dbtest_id_seq" OWNED BY "jos_dbtest.id"', +ALTER COLUMN "id" SET NOT NULL, +ALTER COLUMN "id" SET DEFAULT \'nextval(\'jos_dbtest_id_seq\'::regclass)\'; +ALTER SEQUENCE "jos_dbtest_id_seq" OWNED BY "jos_dbtest.id"', ], [], ], @@ -354,13 +354,13 @@ public static function dataCheck(): array { return [ 'passes checks' => [ - $this->createMock(PgsqlDriver::class), + PgsqlDriver::class, '#__dbtest', null, ], 'fails checks with incorrect database driver subclass' => [ - $this->createMock(DatabaseInterface::class), + DatabaseInterface::class, new \SimpleXMLElement(''), 'Database connection wrong type.', ], @@ -372,7 +372,7 @@ public static function dataCheck(): array ], 'fails checks with no tables' => [ - $this->createMock(PgsqlDriver::class), + PgsqlDriver::class, null, 'ERROR: No Tables Specified', ], From ad1e06ed2442f7995519ba69ddc0e6f9c464d084 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Sun, 13 Jul 2025 16:07:13 +0200 Subject: [PATCH 17/26] Adding baseline for phpstan --- .gitattributes | 1 + phpstan-baseline.neon | 721 ++++++++++++++++++++++++++++++++++++++++++ phpstan.neon | 1 + 3 files changed, 723 insertions(+) create mode 100644 phpstan-baseline.neon diff --git a/.gitattributes b/.gitattributes index a77d5a71d..9333b6632 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,7 @@ Tests/ export-ignore .gitattributes export-ignore .gitignore export-ignore phpstan.neon export-ignore +phpstan-baseline.neon export-ignore phpunit.*.xml.dist export-ignore phpunit.xml.dist export-ignore ruleset.xml export-ignore diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..5d53a9ac0 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,721 @@ +parameters: + ignoreErrors: + - + message: '#^Trait Joomla\\Database\\DatabaseAwareTrait is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: src/DatabaseAwareTrait.php + + - + message: '#^Access to an undefined property Joomla\\Database\\QueryInterface\:\:\$limit\.$#' + identifier: property.notFound + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Access to an undefined property Joomla\\Database\\QueryInterface\:\:\$offset\.$#' + identifier: property.notFound + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Call to function is_object\(\) with array will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Call to function is_string\(\) with non\-falsy\-string will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 11 + path: src/DatabaseDriver.php + + - + message: '#^Instanceof between Joomla\\Database\\QueryInterface and Joomla\\Database\\QueryInterface will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Method Joomla\\Database\\DatabaseDriver\:\:getQuery\(\) should return Joomla\\Database\\DatabaseQuery but returns Joomla\\Database\\QueryInterface\.$#' + identifier: return.type + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Property Joomla\\Database\\DatabaseDriver\:\:\$connection \(resource\) does not accept null\.$#' + identifier: assign.propertyType + count: 2 + path: src/DatabaseDriver.php + + - + message: '#^Property Joomla\\Database\\DatabaseDriver\:\:\$statement \(Joomla\\Database\\StatementInterface\) does not accept null\.$#' + identifier: assign.propertyType + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Strict comparison using \=\=\= between ''resource''\|''resource \(closed\)'' and ''object'' will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Strict comparison using \=\=\= between stdClass and null will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/DatabaseDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 2 + path: src/DatabaseDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 5 + path: src/DatabaseIterator.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 2 + path: src/DatabaseIterator.php + + - + message: '#^Argument of an invalid type \$this\(Joomla\\Database\\DatabaseQuery\) supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 9 + path: src/DatabaseQuery.php + + - + message: '#^Instanceof between Joomla\\Database\\DatabaseInterface and Joomla\\Database\\DatabaseInterface will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 6 + path: src/DatabaseQuery.php + + - + message: '#^Instanceof between string and \$this\(Joomla\\Database\\DatabaseQuery\) will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Left side of \|\| is always true\.$#' + identifier: booleanOr.leftAlwaysTrue + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\|null, string given\.$#' + identifier: argument.type + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Parameter \#2 \$elements of class Joomla\\Database\\Query\\QueryElement constructor expects array\\|string, null given\.$#' + identifier: argument.type + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Property Joomla\\Database\\DatabaseQuery\:\:\$querySet has unknown class Joomla\\Database\\Query\\DatabaseQuery as its type\.$#' + identifier: class.notFound + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Return type \(string\) of method Joomla\\Database\\DatabaseQuery\:\:length\(\) should be compatible with return type \(int\) of method Joomla\\Database\\QueryInterface\:\:length\(\)$#' + identifier: method.childReturnType + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: src/DatabaseQuery.php + + - + message: '#^Call to function is_float\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Mysql/MysqlDriver.php + + - + message: '#^Call to function is_int\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Mysql/MysqlDriver.php + + - + message: '#^Call to function is_string\(\) with non\-falsy\-string will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Mysql/MysqlDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Mysql/MysqlDriver.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Mysql/MysqlDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 1 + path: src/Mysql/MysqlDriver.php + + - + message: '#^Method Joomla\\Database\\Mysql\\MysqlImporter\:\:getKeyLookup\(\) has Exception in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Mysql/MysqlImporter.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Mysql/MysqlQuery.php + + - + message: '#^Call to function is_callable\(\) with array\{mysqli, ''close''\} will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Call to function is_float\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Call to function is_int\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Call to function is_object\(\) with mysqli will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Call to function is_string\(\) with non\-falsy\-string will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Method Joomla\\Database\\Mysqli\\MysqliDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Method Joomla\\Database\\Mysqli\\MysqliDriver\:\:serverClaimsUtf8mb4Support\(\) is unused\.$#' + identifier: method.unused + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^PHPDoc type mysqli of property Joomla\\Database\\Mysqli\\MysqliDriver\:\:\$connection is not covariant with PHPDoc type resource of overridden property Joomla\\Database\\DatabaseDriver\:\:\$connection\.$#' + identifier: property.phpDocType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Property Joomla\\Database\\Mysqli\\MysqliDriver\:\:\$connection \(mysqli\) does not accept null\.$#' + identifier: assign.propertyType + count: 1 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 2 + path: src/Mysqli/MysqliDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Mysqli/MysqliQuery.php + + - + message: '#^PHPDoc type array of property Joomla\\Database\\Mysqli\\MysqliQuery\:\:\$nullDatetimeList is not covariant with PHPDoc type array\ of overridden property Joomla\\Database\\DatabaseQuery\:\:\$nullDatetimeList\.$#' + identifier: property.phpDocType + count: 1 + path: src/Mysqli/MysqliQuery.php + + - + message: '#^PHPDoc tag @param for parameter \$dataType with type int is incompatible with native type string\.$#' + identifier: parameter.phpDocType + count: 1 + path: src/Mysqli/MysqliStatement.php + + - + message: '#^Parameter \#4 \$previous of class Joomla\\Database\\Exception\\ExecutionFailureException constructor expects Exception\|null, Throwable given\.$#' + identifier: argument.type + count: 1 + path: src/Mysqli/MysqliStatement.php + + - + message: '#^Return type \(int\) of method Joomla\\Database\\Mysqli\\MysqliStatement\:\:errorCode\(\) should be compatible with return type \(string\) of method Joomla\\Database\\StatementInterface\:\:errorCode\(\)$#' + identifier: method.childReturnType + count: 1 + path: src/Mysqli/MysqliStatement.php + + - + message: '#^Return type \(string\) of method Joomla\\Database\\Mysqli\\MysqliStatement\:\:errorInfo\(\) should be compatible with return type \(array\) of method Joomla\\Database\\StatementInterface\:\:errorInfo\(\)$#' + identifier: method.childReturnType + count: 1 + path: src/Mysqli/MysqliStatement.php + + - + message: '#^Strict comparison using \=\=\= between array\|bool and null will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/Mysqli/MysqliStatement.php + + - + message: '#^Call to function is_float\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Pdo/PdoDriver.php + + - + message: '#^Call to function is_int\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Pdo/PdoDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 4 + path: src/Pdo/PdoDriver.php + + - + message: '#^Method Joomla\\Database\\Pdo\\PdoDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Pdo/PdoDriver.php + + - + message: '#^PHPDoc type PDO of property Joomla\\Database\\Pdo\\PdoDriver\:\:\$connection is not covariant with PHPDoc type resource of overridden property Joomla\\Database\\DatabaseDriver\:\:\$connection\.$#' + identifier: property.phpDocType + count: 1 + path: src/Pdo/PdoDriver.php + + - + message: '#^Property Joomla\\Database\\Pdo\\PdoDriver\:\:\$connection \(PDO\) does not accept null\.$#' + identifier: assign.propertyType + count: 1 + path: src/Pdo/PdoDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 1 + path: src/Pdo/PdoDriver.php + + - + message: '#^PHPDoc type array of property Joomla\\Database\\Pdo\\PdoQuery\:\:\$nullDatetimeList is not covariant with PHPDoc type array\ of overridden property Joomla\\Database\\DatabaseQuery\:\:\$nullDatetimeList\.$#' + identifier: property.phpDocType + count: 1 + path: src/Pdo/PdoQuery.php + + - + message: '#^Call to function is_object\(\) with array will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Method Joomla\\Database\\Pgsql\\PgsqlDriver\:\:getTableCreate\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Strict comparison using \=\=\= between string and 0 will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Strict comparison using \=\=\= between string and 1 will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Strict comparison using \=\=\= between string and false will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Strict comparison using \=\=\= between string and true will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 1 + path: src/Pgsql/PgsqlDriver.php + + - + message: '#^Instanceof between Joomla\\Database\\Pgsql\\PgsqlDriver and Joomla\\Database\\Pgsql\\PgsqlDriver will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: src/Pgsql/PgsqlExporter.php + + - + message: '#^Cannot access property \$Index on array\.$#' + identifier: property.nonObject + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Cannot access property \$Key_name on array\.$#' + identifier: property.nonObject + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Instanceof between Joomla\\Database\\Pgsql\\PgsqlDriver and Joomla\\Database\\Pgsql\\PgsqlDriver will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Instanceof between array and SimpleXMLElement will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Method Joomla\\Database\\Pgsql\\PgsqlImporter\:\:getChangeSequenceSql\(\) invoked with 2 parameters, 1 required\.$#' + identifier: arguments.count + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Method Joomla\\Database\\Pgsql\\PgsqlImporter\:\:getSetvalSequenceSql\(\) invoked with 2 parameters, 1 required\.$#' + identifier: arguments.count + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Parameter \#1 \$field of method Joomla\\Database\\Pgsql\\PgsqlImporter\:\:getChangeSequenceSql\(\) expects SimpleXMLElement, \(int\|string\) given\.$#' + identifier: argument.type + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Parameter \#1 \$field of method Joomla\\Database\\Pgsql\\PgsqlImporter\:\:getSetvalSequenceSql\(\) expects SimpleXMLElement, \(int\|string\) given\.$#' + identifier: argument.type + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^Parameter \#2 \$key of method Joomla\\Database\\Pgsql\\PgsqlImporter\:\:getAddUniqueSql\(\) expects array, SimpleXMLElement given\.$#' + identifier: argument.type + count: 1 + path: src/Pgsql/PgsqlImporter.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 12 + path: src/Pgsql/PgsqlQuery.php + + - + message: '#^Instanceof between string and \$this\(Joomla\\Database\\Pgsql\\PgsqlQuery\) will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse + count: 1 + path: src/Pgsql/PgsqlQuery.php + + - + message: '#^Parameter \#2 \$elements of class Joomla\\Database\\Query\\QueryElement constructor expects array\\|string, int given\.$#' + identifier: argument.type + count: 2 + path: src/Pgsql/PgsqlQuery.php + + - + message: '#^Parameter \#2 \$elements of class Joomla\\Database\\Query\\QueryElement constructor expects array\\|string, null given\.$#' + identifier: argument.type + count: 1 + path: src/Pgsql/PgsqlQuery.php + + - + message: '#^Ternary operator condition is always true\.$#' + identifier: ternary.alwaysTrue + count: 1 + path: src/Pgsql/PgsqlQuery.php + + - + message: '#^Argument of an invalid type \$this\(Joomla\\Database\\Query\\QueryElement\) supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: src/Query/QueryElement.php + + - + message: '#^Call to function is_float\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Call to function is_int\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:alterDbCharacterSet\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:createDatabase\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:getConnectionCollation\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:getConnectionEncryption\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:lockTable\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:unlockTables\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 1 + path: src/Sqlite/SqliteDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 1 + path: src/Sqlite/SqliteQuery.php + + - + message: '#^Left side of \|\| is always true\.$#' + identifier: booleanOr.leftAlwaysTrue + count: 1 + path: src/Sqlite/SqliteQuery.php + + - + message: '#^Result of \|\| is always true\.$#' + identifier: booleanOr.alwaysTrue + count: 1 + path: src/Sqlite/SqliteQuery.php + + - + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: src/Sqlite/SqliteQuery.php + + - + message: '#^Call to function is_float\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Call to function is_int\(\) with string will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 4 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlsrv\\SqlsrvDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlsrv\\SqlsrvDriver\:\:lockTable\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Method Joomla\\Database\\Sqlsrv\\SqlsrvDriver\:\:unlockTables\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Parameter \#3 \$params of function sqlsrv_query expects array, null given\.$#' + identifier: argument.type + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Property Joomla\\Database\\DatabaseDriver\:\:\$connection \(resource\) does not accept null\.$#' + identifier: assign.propertyType + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 1 + path: src/Sqlsrv/SqlsrvDriver.php + + - + message: '#^Comparison operation "\>" between 0 and 1 is always false\.$#' + identifier: greater.alwaysFalse + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 14 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^Instanceof between Joomla\\Database\\DatabaseInterface and Joomla\\Database\\DatabaseInterface will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^Left side of \|\| is always true\.$#' + identifier: booleanOr.leftAlwaysTrue + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^Offset int\<1, max\> on list in isset\(\) does not exist\.$#' + identifier: isset.offset + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^PHPDoc type array of property Joomla\\Database\\Sqlsrv\\SqlsrvQuery\:\:\$nullDatetimeList is not covariant with PHPDoc type array\ of overridden property Joomla\\Database\\DatabaseQuery\:\:\$nullDatetimeList\.$#' + identifier: property.phpDocType + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, array\ given\.$#' + identifier: argument.type + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^Result of \|\| is always true\.$#' + identifier: booleanOr.alwaysTrue + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php + + - + message: '#^Return type \(string\) of method Joomla\\Database\\Sqlsrv\\SqlsrvQuery\:\:length\(\) should be compatible with return type \(int\) of method Joomla\\Database\\QueryInterface\:\:length\(\)$#' + identifier: method.childReturnType + count: 1 + path: src/Sqlsrv/SqlsrvQuery.php diff --git a/phpstan.neon b/phpstan.neon index 07d822700..305d72f17 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,7 @@ includes: - vendor/phpstan/phpstan-deprecation-rules/rules.neon + - phpstan-baseline.neon parameters: level: 5 From 8e1115319eb8936ef4a0e0a6f8731aacf98422a5 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Sun, 13 Jul 2025 22:22:54 +0200 Subject: [PATCH 18/26] Fix logging tests --- Tests/Monitor/LoggingMonitorTest.php | 2 +- composer.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Monitor/LoggingMonitorTest.php b/Tests/Monitor/LoggingMonitorTest.php index 074fc52e5..5465e31a6 100644 --- a/Tests/Monitor/LoggingMonitorTest.php +++ b/Tests/Monitor/LoggingMonitorTest.php @@ -8,7 +8,7 @@ use Joomla\Database\Monitor\LoggingMonitor; use PHPUnit\Framework\TestCase; -use Psr\Log\Test\TestLogger; +use ColinODell\PsrTestLogger\TestLogger; /** * Test class for Joomla\Database\Monitor\LoggingMonitor diff --git a/composer.json b/composer.json index 4b91c0211..35172e6ad 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "joomla/test": "dev-4.x-dev", "phpunit/phpunit": "^12.0", "psr/log": "^3.0.2", + "colinodell/psr-testlogger": "^1.3.0", "symfony/phpunit-bridge": "^8.0", "squizlabs/php_codesniffer": "~3.10.2", "phpstan/phpstan": "^2.1.17", From d6aa2bf20bad99984c6b1c4a8cce551217e5589e Mon Sep 17 00:00:00 2001 From: Robert Deutz Date: Wed, 16 Jul 2025 16:28:18 +0200 Subject: [PATCH 19/26] Revert bc breaks (#344) * fix b/c break * update baseline --------- Co-authored-by: Robert Deutz --- phpstan-baseline.neon | 18 ++++++++++++++++++ src/DatabaseDriver.php | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5d53a9ac0..e00f4195a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -36,6 +36,12 @@ parameters: count: 1 path: src/DatabaseDriver.php + - + message: '#^Call to function is_string\(\) with string will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/DatabaseDriver.php + - message: '#^If condition is always true\.$#' identifier: if.alwaysTrue @@ -72,6 +78,12 @@ parameters: count: 1 path: src/DatabaseDriver.php + - + message: '#^Result of \|\| is always false\.$#' + identifier: booleanOr.alwaysFalse + count: 1 + path: src/DatabaseDriver.php + - message: '#^Strict comparison using \=\=\= between ''resource''\|''resource \(closed\)'' and ''object'' will always evaluate to false\.$#' identifier: identical.alwaysFalse @@ -84,6 +96,12 @@ parameters: count: 1 path: src/DatabaseDriver.php + - + message: '#^Strict comparison using \=\=\= between string and null will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/DatabaseDriver.php + - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php index b56d92296..81885d022 100644 --- a/src/DatabaseDriver.php +++ b/src/DatabaseDriver.php @@ -504,8 +504,12 @@ public function __destruct() * * @since 2.0.0 */ - public function alterDbCharacterSet(string $dbName) + public function alterDbCharacterSet($dbName) { + if ($dbName === null || !is_string($dbName) || $dbName === '') { + throw new \RuntimeException('Database name must not be null and a non empty string.'); + } + $this->setQuery($this->getAlterDbCharacterSet($dbName)); return $this->execute(); From 2e744e7959368891fe52ccb35790e201251f6f39 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Thu, 24 Jul 2025 11:46:18 +0200 Subject: [PATCH 20/26] Joomla! Framework v4.0.0 --- composer.json | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 35172e6ad..e76772d06 100644 --- a/composer.json +++ b/composer.json @@ -7,21 +7,21 @@ "license": "GPL-2.0-or-later", "require": { "php": "^8.3.0", - "joomla/event": "dev-4.x-dev", + "joomla/event": "^4.0", "symfony/deprecation-contracts": "^2|^3" }, "require-dev": { - "joomla/archive": "dev-4.x-dev", - "joomla/console": "dev-4.x-dev", - "joomla/di": "dev-4.x-dev", - "joomla/filesystem": "dev-4.x-dev", - "joomla/registry": "dev-4.x-dev", - "joomla/test": "dev-4.x-dev", + "joomla/archive": "^4.0", + "joomla/console": "^4.0", + "joomla/di": "^4.0", + "joomla/filesystem": "^4.0", + "joomla/registry": "^4.0", + "joomla/test": "^4.0", "phpunit/phpunit": "^12.0", "psr/log": "^3.0.2", "colinodell/psr-testlogger": "^1.3.0", "symfony/phpunit-bridge": "^8.0", - "squizlabs/php_codesniffer": "~3.10.2", + "squizlabs/php_codesniffer": "^3.10.2", "phpstan/phpstan": "^2.1.17", "phpstan/phpstan-deprecation-rules": "^2.0.3" }, @@ -46,12 +46,5 @@ "Joomla\\Database\\Tests\\": "Tests/" } }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-2.0-dev": "2.0-dev", - "dev-3.x-dev": "3.0-dev", - "dev-4.x-dev": "4.0-dev" - } - } + "minimum-stability": "dev" } From 02c86bd3e28c781da4d9fbf601e0b95ae9a1d05d Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:00:51 -0600 Subject: [PATCH 21/26] phpstan should fail ci action like rest of framework packages --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9364eaa08..51fca5c22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,6 @@ jobs: runs-on: ubuntu-latest container: joomlaprojects/docker-images:php8.4 needs: [code-style-php] - continue-on-error: true steps: - uses: actions/checkout@v4 - uses: actions/cache/restore@v4 From 5f202c3f88c616826c7e7d751133e047658a4a22 Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:01:35 -0600 Subject: [PATCH 22/26] return type should match with class 'DatabaseDriver' --- phpstan-baseline.neon | 2 +- src/DatabaseInterface.php | 2 +- src/DatabaseQuery.php | 2 +- src/Query/MysqlQueryBuilder.php | 2 +- src/Sqlsrv/SqlsrvDriver.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e00f4195a..23159a074 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -663,7 +663,7 @@ parameters: - message: '#^Parameter \#2 \$array of function implode expects array\, list\ given\.$#' identifier: argument.type - count: 1 + count: 2 path: src/Sqlsrv/SqlsrvDriver.php - diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php index 9b65b24ba..5f3e41c95 100644 --- a/src/DatabaseInterface.php +++ b/src/DatabaseInterface.php @@ -470,7 +470,7 @@ public function lockTable($tableName); * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * - * @return string + * @return array|string The quoted input string. * * @since 2.0.0 */ diff --git a/src/DatabaseQuery.php b/src/DatabaseQuery.php index e7866ab52..b724e4592 100644 --- a/src/DatabaseQuery.php +++ b/src/DatabaseQuery.php @@ -1466,7 +1466,7 @@ public function q($text, $escape = true) * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * - * @return string The quoted input string. + * @return array|string The quoted input string. * * @since 1.0 * @throws \RuntimeException if the internal db property is not a valid object. diff --git a/src/Query/MysqlQueryBuilder.php b/src/Query/MysqlQueryBuilder.php index 1ce5000be..7ac3139b6 100644 --- a/src/Query/MysqlQueryBuilder.php +++ b/src/Query/MysqlQueryBuilder.php @@ -138,7 +138,7 @@ public function groupConcat($expression, $separator = ',') * @param array|string $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * - * @return string The quoted input string. + * @return array|string The quoted input string. * * @since 2.0.0 * @throws \RuntimeException if the internal db property is not a valid object. diff --git a/src/Sqlsrv/SqlsrvDriver.php b/src/Sqlsrv/SqlsrvDriver.php index dadbb27a6..e2a430c71 100644 --- a/src/Sqlsrv/SqlsrvDriver.php +++ b/src/Sqlsrv/SqlsrvDriver.php @@ -249,7 +249,7 @@ public function escape($text, $extra = false) * @param mixed $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * - * @return string The quoted input string. + * @return array|string The quoted input string. * * @since 1.6.0 */ From a9811678c1ad4240d026200a5e0db181800d343b Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:02:20 -0600 Subject: [PATCH 23/26] properties accept null --- src/DatabaseQuery.php | 68 ++++++++++++++-------------- src/Query/PostgresqlQueryBuilder.php | 8 ++-- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/DatabaseQuery.php b/src/DatabaseQuery.php index b724e4592..e2d53a1bc 100644 --- a/src/DatabaseQuery.php +++ b/src/DatabaseQuery.php @@ -20,26 +20,26 @@ * @property-read array $bounded Holds key / value pair of bound objects. * @property-read array $parameterMapping Mapping array for parameter types. * @property-read DatabaseInterface $db The database driver. - * @property-read string $sql The SQL query (if a direct query string was provided). + * @property-read string|null $sql The SQL query (if a direct query string was provided). * @property-read string $type The query type. * @property-read string|null $alias The query alias. * @property-read Query\QueryElement $element The query element for a generic query (type = null). - * @property-read Query\QueryElement $select The select element. - * @property-read Query\QueryElement $delete The delete element. - * @property-read Query\QueryElement $update The update element. - * @property-read Query\QueryElement $insert The insert element. - * @property-read Query\QueryElement $from The from element. + * @property-read Query\QueryElement|null $select The select element. + * @property-read Query\QueryElement|null $delete The delete element. + * @property-read Query\QueryElement|null $update The update element. + * @property-read Query\QueryElement|null $insert The insert element. + * @property-read Query\QueryElement|null $from The from element. * @property-read Query\QueryElement[]|null $join The join elements. - * @property-read Query\QueryElement $set The set element. - * @property-read Query\QueryElement $where The where element. - * @property-read Query\QueryElement $group The group element. - * @property-read Query\QueryElement $having The having element. - * @property-read Query\QueryElement $columns The column list for an INSERT statement. - * @property-read Query\QueryElement $values The values list for an INSERT statement. - * @property-read Query\QueryElement $order The order element. - * @property-read boolean $autoIncrementField The auto increment insert field element. - * @property-read Query\QueryElement $call The call element. - * @property-read Query\QueryElement $exec The exec element. + * @property-read Query\QueryElement|null $set The set element. + * @property-read Query\QueryElement|null $where The where element. + * @property-read Query\QueryElement|null $group The group element. + * @property-read Query\QueryElement|null $having The having element. + * @property-read Query\QueryElement|null $columns The column list for an INSERT statement. + * @property-read Query\QueryElement|null $values The values list for an INSERT statement. + * @property-read Query\QueryElement|null $order The order element. + * @property-read boolean|null $autoIncrementField The auto increment insert field element. + * @property-read Query\QueryElement|null $call The call element. + * @property-read Query\QueryElement|null $exec The exec element. * @property-read Query\QueryElement[]|null $merge The list of query elements. * @property-read DatabaseQuery|null $querySet The query object. * @property-read array|null $selectRowNumber Details of window function. @@ -83,7 +83,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The SQL query (if a direct query string was provided). * - * @var string + * @var ?string * @since 1.0 */ protected $sql; @@ -115,7 +115,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The select element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $select; @@ -123,7 +123,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The delete element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $delete; @@ -131,7 +131,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The update element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $update; @@ -139,7 +139,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The insert element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $insert; @@ -147,7 +147,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The from element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $from; @@ -155,7 +155,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The join elements. * - * @var Query\QueryElement[] + * @var ?Query\QueryElement[] * @since 1.0 */ protected $join; @@ -163,7 +163,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The set element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $set; @@ -171,7 +171,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The where element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $where; @@ -179,7 +179,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The group by element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $group; @@ -187,7 +187,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The having element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $having; @@ -195,7 +195,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The column list for an INSERT statement. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $columns; @@ -203,7 +203,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The values list for an INSERT statement. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $values; @@ -211,7 +211,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The order element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $order; @@ -219,7 +219,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The auto increment insert field element. * - * @var boolean + * @var ?boolean * @since 1.0 */ protected $autoIncrementField = false; @@ -227,7 +227,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The call element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $call; @@ -235,7 +235,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The exec element. * - * @var Query\QueryElement + * @var ?Query\QueryElement * @since 1.0 */ protected $exec; @@ -243,7 +243,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The list of query elements, which may include UNION, UNION ALL, EXCEPT and INTERSECT. * - * @var Query\QueryElement[] + * @var ?Query\QueryElement[] * @since 2.0.0 */ protected $merge; diff --git a/src/Query/PostgresqlQueryBuilder.php b/src/Query/PostgresqlQueryBuilder.php index d54464e5c..a520fc5ed 100644 --- a/src/Query/PostgresqlQueryBuilder.php +++ b/src/Query/PostgresqlQueryBuilder.php @@ -19,7 +19,7 @@ trait PostgresqlQueryBuilder /** * The FOR UPDATE element used in "FOR UPDATE" lock * - * @var QueryElement + * @var ?QueryElement * @since 2.0.0 */ protected $forUpdate; @@ -27,7 +27,7 @@ trait PostgresqlQueryBuilder /** * The FOR SHARE element used in "FOR SHARE" lock * - * @var QueryElement + * @var ?QueryElement * @since 2.0.0 */ protected $forShare; @@ -35,7 +35,7 @@ trait PostgresqlQueryBuilder /** * The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock * - * @var QueryElement + * @var ?QueryElement * @since 2.0.0 */ protected $noWait; @@ -59,7 +59,7 @@ trait PostgresqlQueryBuilder /** * The RETURNING element of INSERT INTO * - * @var QueryElement + * @var ?QueryElement * @since 2.0.0 */ protected $returning; From 8a22203959c2636b7a3e1662ece0545d04c1263f Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:04:06 -0600 Subject: [PATCH 24/26] fix phpdoc type class not exist --- src/DatabaseQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseQuery.php b/src/DatabaseQuery.php index e2d53a1bc..6abc16f8b 100644 --- a/src/DatabaseQuery.php +++ b/src/DatabaseQuery.php @@ -251,7 +251,7 @@ abstract class DatabaseQuery implements QueryInterface /** * The query object. * - * @var Query\DatabaseQuery + * @var ?DatabaseQuery * @since 2.0.0 */ protected $querySet; From 54528bd65abbf78aaf8944744f881231924355e1 Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:04:22 -0600 Subject: [PATCH 25/26] clean baseline --- phpstan-baseline.neon | 174 ------------------------------------------ 1 file changed, 174 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 23159a074..937e2ad73 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -126,12 +126,6 @@ parameters: count: 1 path: src/DatabaseQuery.php - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 9 - path: src/DatabaseQuery.php - - message: '#^Instanceof between Joomla\\Database\\DatabaseInterface and Joomla\\Database\\DatabaseInterface will always evaluate to true\.$#' identifier: instanceof.alwaysTrue @@ -144,30 +138,12 @@ parameters: count: 1 path: src/DatabaseQuery.php - - - message: '#^Left side of \|\| is always true\.$#' - identifier: booleanOr.leftAlwaysTrue - count: 1 - path: src/DatabaseQuery.php - - - - message: '#^Parameter \#2 \$array of function implode expects array\|null, string given\.$#' - identifier: argument.type - count: 1 - path: src/DatabaseQuery.php - - message: '#^Parameter \#2 \$elements of class Joomla\\Database\\Query\\QueryElement constructor expects array\\|string, null given\.$#' identifier: argument.type count: 1 path: src/DatabaseQuery.php - - - message: '#^Property Joomla\\Database\\DatabaseQuery\:\:\$querySet has unknown class Joomla\\Database\\Query\\DatabaseQuery as its type\.$#' - identifier: class.notFound - count: 1 - path: src/DatabaseQuery.php - - message: '#^Return type \(string\) of method Joomla\\Database\\DatabaseQuery\:\:length\(\) should be compatible with return type \(int\) of method Joomla\\Database\\QueryInterface\:\:length\(\)$#' identifier: method.childReturnType @@ -216,18 +192,6 @@ parameters: count: 1 path: src/Mysql/MysqlDriver.php - - - message: '#^Method Joomla\\Database\\Mysql\\MysqlImporter\:\:getKeyLookup\(\) has Exception in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Mysql/MysqlImporter.php - - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 1 - path: src/Mysql/MysqlQuery.php - - message: '#^Call to function is_callable\(\) with array\{mysqli, ''close''\} will always evaluate to true\.$#' identifier: function.alreadyNarrowedType @@ -264,12 +228,6 @@ parameters: count: 1 path: src/Mysqli/MysqliDriver.php - - - message: '#^Method Joomla\\Database\\Mysqli\\MysqliDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Mysqli/MysqliDriver.php - - message: '#^Method Joomla\\Database\\Mysqli\\MysqliDriver\:\:serverClaimsUtf8mb4Support\(\) is unused\.$#' identifier: method.unused @@ -300,12 +258,6 @@ parameters: count: 2 path: src/Mysqli/MysqliDriver.php - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 1 - path: src/Mysqli/MysqliQuery.php - - message: '#^PHPDoc type array of property Joomla\\Database\\Mysqli\\MysqliQuery\:\:\$nullDatetimeList is not covariant with PHPDoc type array\ of overridden property Joomla\\Database\\DatabaseQuery\:\:\$nullDatetimeList\.$#' identifier: property.phpDocType @@ -360,12 +312,6 @@ parameters: count: 4 path: src/Pdo/PdoDriver.php - - - message: '#^Method Joomla\\Database\\Pdo\\PdoDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Pdo/PdoDriver.php - - message: '#^PHPDoc type PDO of property Joomla\\Database\\Pdo\\PdoDriver\:\:\$connection is not covariant with PHPDoc type resource of overridden property Joomla\\Database\\DatabaseDriver\:\:\$connection\.$#' identifier: property.phpDocType @@ -402,12 +348,6 @@ parameters: count: 1 path: src/Pgsql/PgsqlDriver.php - - - message: '#^Method Joomla\\Database\\Pgsql\\PgsqlDriver\:\:getTableCreate\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Pgsql/PgsqlDriver.php - - message: '#^Strict comparison using \=\=\= between string and 0 will always evaluate to false\.$#' identifier: identical.alwaysFalse @@ -498,12 +438,6 @@ parameters: count: 1 path: src/Pgsql/PgsqlImporter.php - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 12 - path: src/Pgsql/PgsqlQuery.php - - message: '#^Instanceof between string and \$this\(Joomla\\Database\\Pgsql\\PgsqlQuery\) will always evaluate to false\.$#' identifier: instanceof.alwaysFalse @@ -522,12 +456,6 @@ parameters: count: 1 path: src/Pgsql/PgsqlQuery.php - - - message: '#^Ternary operator condition is always true\.$#' - identifier: ternary.alwaysTrue - count: 1 - path: src/Pgsql/PgsqlQuery.php - - message: '#^Argument of an invalid type \$this\(Joomla\\Database\\Query\\QueryElement\) supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -552,72 +480,12 @@ parameters: count: 1 path: src/Sqlite/SqliteDriver.php - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:alterDbCharacterSet\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:createDatabase\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:getConnectionCollation\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:getConnectionEncryption\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:lockTable\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlite\\SqliteDriver\:\:unlockTables\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlite/SqliteDriver.php - - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable count: 1 path: src/Sqlite/SqliteDriver.php - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 1 - path: src/Sqlite/SqliteQuery.php - - - - message: '#^Left side of \|\| is always true\.$#' - identifier: booleanOr.leftAlwaysTrue - count: 1 - path: src/Sqlite/SqliteQuery.php - - - - message: '#^Result of \|\| is always true\.$#' - identifier: booleanOr.alwaysTrue - count: 1 - path: src/Sqlite/SqliteQuery.php - - message: '#^Unsafe usage of new static\(\)\.$#' identifier: new.static @@ -642,24 +510,6 @@ parameters: count: 4 path: src/Sqlsrv/SqlsrvDriver.php - - - message: '#^Method Joomla\\Database\\Sqlsrv\\SqlsrvDriver\:\:connect\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlsrv/SqlsrvDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlsrv\\SqlsrvDriver\:\:lockTable\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlsrv/SqlsrvDriver.php - - - - message: '#^Method Joomla\\Database\\Sqlsrv\\SqlsrvDriver\:\:unlockTables\(\) has RuntimeException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/Sqlsrv/SqlsrvDriver.php - - message: '#^Parameter \#2 \$array of function implode expects array\, list\ given\.$#' identifier: argument.type @@ -690,24 +540,12 @@ parameters: count: 1 path: src/Sqlsrv/SqlsrvQuery.php - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 14 - path: src/Sqlsrv/SqlsrvQuery.php - - message: '#^Instanceof between Joomla\\Database\\DatabaseInterface and Joomla\\Database\\DatabaseInterface will always evaluate to true\.$#' identifier: instanceof.alwaysTrue count: 1 path: src/Sqlsrv/SqlsrvQuery.php - - - message: '#^Left side of \|\| is always true\.$#' - identifier: booleanOr.leftAlwaysTrue - count: 1 - path: src/Sqlsrv/SqlsrvQuery.php - - message: '#^Offset int\<1, max\> on list in isset\(\) does not exist\.$#' identifier: isset.offset @@ -720,18 +558,6 @@ parameters: count: 1 path: src/Sqlsrv/SqlsrvQuery.php - - - message: '#^Parameter \#2 \$array of function implode expects array\, array\ given\.$#' - identifier: argument.type - count: 1 - path: src/Sqlsrv/SqlsrvQuery.php - - - - message: '#^Result of \|\| is always true\.$#' - identifier: booleanOr.alwaysTrue - count: 1 - path: src/Sqlsrv/SqlsrvQuery.php - - message: '#^Return type \(string\) of method Joomla\\Database\\Sqlsrv\\SqlsrvQuery\:\:length\(\) should be compatible with return type \(int\) of method Joomla\\Database\\QueryInterface\:\:length\(\)$#' identifier: method.childReturnType From 60f89b05fa93f53ba121c7c90227aa1246984e50 Mon Sep 17 00:00:00 2001 From: Christian Heel <66922325+heelc29@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:04:40 -0600 Subject: [PATCH 26/26] update baseline --- phpstan-baseline.neon | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 937e2ad73..362465074 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -456,6 +456,18 @@ parameters: count: 1 path: src/Pgsql/PgsqlQuery.php + - + message: '#^Property Joomla\\Database\\DatabaseQuery\:\:\$limit \(int\|null\) does not accept Joomla\\Database\\Query\\QueryElement\.$#' + identifier: assign.propertyType + count: 1 + path: src/Pgsql/PgsqlQuery.php + + - + message: '#^Property Joomla\\Database\\DatabaseQuery\:\:\$offset \(int\|null\) does not accept Joomla\\Database\\Query\\QueryElement\.$#' + identifier: assign.propertyType + count: 1 + path: src/Pgsql/PgsqlQuery.php + - message: '#^Argument of an invalid type \$this\(Joomla\\Database\\Query\\QueryElement\) supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable