From a88ad8d478c3c0dbf3e89a71ca7fd5c4bf4af838 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Wed, 22 Oct 2025 13:05:52 -0300 Subject: [PATCH 1/7] Make test data providers static and declare array return types in `QueryBuilderTest` class. --- tests/framework/db/QueryBuilderTest.php | 139 +++++++++------- .../framework/db/cubrid/QueryBuilderTest.php | 42 +++-- tests/framework/db/mssql/QueryBuilderTest.php | 62 ++++---- tests/framework/db/mysql/QueryBuilderTest.php | 45 +++--- tests/framework/db/oci/QueryBuilderTest.php | 90 +++++------ tests/framework/db/pgsql/QueryBuilderTest.php | 41 ++--- .../framework/db/sqlite/QueryBuilderTest.php | 149 +++++++++++------- tests/framework/support/DbHelper.php | 43 ----- 8 files changed, 320 insertions(+), 291 deletions(-) delete mode 100644 tests/framework/support/DbHelper.php diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 748b6a81f28..117d9db8fe7 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -1244,9 +1244,9 @@ public static function conditionProvider(): array ]; } - public function filterConditionProvider() + public static function filterConditionProvider(): array { - $conditions = [ + return [ // like [['like', 'name', []], '', []], [['not like', 'name', []], '', []], @@ -1284,13 +1284,6 @@ public function filterConditionProvider() [['<>', 'a', ''], '', []], [['!=', 'a', ''], '', []], ]; - - // adjust dbms specific escaping - foreach ($conditions as $i => $condition) { - $conditions[$i][1] = $this->replaceQuotes($condition[1]); - } - - return $conditions; } /** @@ -1306,7 +1299,7 @@ public function testBuildFrom($table, $expected): void $this->assertEquals('FROM ' . $this->replaceQuotes($expected), $sql); } - public function buildFromDataProvider() + public static function buildFromDataProvider(): array { return [ ['test t1', '[[test]] [[t1]]'], @@ -1345,7 +1338,7 @@ public function testBuildFilterCondition($condition, $expected, $expectedParams) $this->assertEquals($expectedParams, $params); } - public function primaryKeysProvider() + public static function primaryKeysProvider(): array { $tableName = 'T_constraints_1'; $name = 'CN_pk'; @@ -1380,7 +1373,7 @@ public function testAddDropPrimaryKey($sql, Closure $builder): void $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false))); } - public function foreignKeysProvider() + public static function foreignKeysProvider(): array { $tableName = 'T_constraints_3'; $name = 'CN_constraints_3'; @@ -1416,7 +1409,7 @@ public function testAddDropForeignKey($sql, Closure $builder): void $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false))); } - public function indexesProvider() + public static function indexesProvider(): array { $tableName = 'T_constraints_2'; $name1 = 'CN_constraints_2_single'; @@ -1464,7 +1457,7 @@ public function testCreateDropIndex($sql, Closure $builder): void $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false))); } - public function uniquesProvider() + public static function uniquesProvider(): array { $tableName1 = 'T_constraints_1'; $name1 = 'CN_unique'; @@ -1501,7 +1494,7 @@ public function testAddDropUnique($sql, Closure $builder): void $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false))); } - public function checksProvider() + public static function checksProvider(): array { $tableName = 'T_constraints_1'; $name = 'CN_check'; @@ -1530,7 +1523,7 @@ public function testAddDropCheck($sql, Closure $builder): void $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false))); } - public function defaultValuesProvider() + public static function defaultValuesProvider(): array { $tableName = 'T_constraints_1'; $name = 'CN_default'; @@ -1559,11 +1552,17 @@ public function testAddDropDefaultValue($sql, Closure $builder): void $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false))); } - public function existsParamsProvider() + public static function existsParamsProvider(): array { return [ - ['exists', $this->replaceQuotes('SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE EXISTS (SELECT [[1]] FROM [[Website]] [[w]])')], - ['not exists', $this->replaceQuotes('SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE NOT EXISTS (SELECT [[1]] FROM [[Website]] [[w]])')], + [ + 'exists', + 'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE EXISTS (SELECT [[1]] FROM [[Website]] [[w]])', + ], + [ + 'not exists', + 'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE NOT EXISTS (SELECT [[1]] FROM [[Website]] [[w]])', + ], ]; } @@ -1577,16 +1576,14 @@ public function testBuildWhereExists($cond, $expectedQuerySql): void $expectedQueryParams = []; $subQuery = new Query(); - $subQuery->select('1') - ->from('Website w'); + $subQuery->select('1')->from('Website w'); $query = new Query(); - $query->select('id') - ->from('TotalExample t') - ->where([$cond, $subQuery]); + $query->select('id')->from('TotalExample t')->where([$cond, $subQuery]); list($actualQuerySql, $actualQueryParams) = $this->getQueryBuilder()->build($query); - $this->assertEquals($expectedQuerySql, $actualQuerySql); + + $this->assertEquals($this->replaceQuotes($expectedQuerySql), $actualQuerySql); $this->assertEquals($expectedQueryParams, $actualQueryParams); } @@ -1923,7 +1920,7 @@ public function testGroupBy(): void $this->assertEquals([':to' => 4], $params); } - public function insertProvider() + public static function insertProvider(): array { return [ 'regular-values' => [ @@ -1936,7 +1933,7 @@ public function insertProvider() 'related_id' => null, ], [], - $this->replaceQuotes('INSERT INTO [[customer]] ([[email]], [[name]], [[address]], [[is_active]], [[related_id]]) VALUES (:qp0, :qp1, :qp2, :qp3, :qp4)'), + 'INSERT INTO [[customer]] ([[email]], [[name]], [[address]], [[is_active]], [[related_id]]) VALUES (:qp0, :qp1, :qp2, :qp3, :qp4)', [ ':qp0' => 'test@example.com', ':qp1' => 'silverfire', @@ -1956,6 +1953,7 @@ public function insertProvider() [ ':qp0' => null, ], + false, ], 'carry passed params' => [ 'customer', @@ -1968,7 +1966,7 @@ public function insertProvider() 'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']), ], [':phBar' => 'bar'], - $this->replaceQuotes('INSERT INTO [[customer]] ([[email]], [[name]], [[address]], [[is_active]], [[related_id]], [[col]]) VALUES (:qp1, :qp2, :qp3, :qp4, :qp5, CONCAT(:phFoo, :phBar))'), + 'INSERT INTO [[customer]] ([[email]], [[name]], [[address]], [[is_active]], [[related_id]], [[col]]) VALUES (:qp1, :qp2, :qp3, :qp4, :qp5, CONCAT(:phFoo, :phBar))', [ ':phBar' => 'bar', ':qp1' => 'test@example.com', @@ -1999,7 +1997,7 @@ public function insertProvider() 'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']), ]), [':phBar' => 'bar'], - $this->replaceQuotes('INSERT INTO [[customer]] ([[email]], [[name]], [[address]], [[is_active]], [[related_id]]) SELECT [[email]], [[name]], [[address]], [[is_active]], [[related_id]] FROM [[customer]] WHERE ([[email]]=:qp1) AND ([[name]]=:qp2) AND ([[address]]=:qp3) AND ([[is_active]]=:qp4) AND ([[related_id]] IS NULL) AND ([[col]]=CONCAT(:phFoo, :phBar))'), + 'INSERT INTO [[customer]] ([[email]], [[name]], [[address]], [[is_active]], [[related_id]]) SELECT [[email]], [[name]], [[address]], [[is_active]], [[related_id]] FROM [[customer]] WHERE ([[email]]=:qp1) AND ([[name]]=:qp2) AND ([[address]]=:qp3) AND ([[is_active]]=:qp4) AND ([[related_id]] IS NULL) AND ([[col]]=CONCAT(:phFoo, :phBar))', [ ':phBar' => 'bar', ':qp1' => 'test@example.com', @@ -2019,11 +2017,17 @@ public function insertProvider() * @param array $params * @param string $expectedSQL * @param array $expectedParams + * @param array $replaceQuotes */ - public function testInsert($table, $columns, $params, $expectedSQL, $expectedParams): void + public function testInsert($table, $columns, $params, $expectedSQL, $expectedParams, $replaceQuotes = true): void { $actualParams = $params; $actualSQL = $this->getQueryBuilder()->insert($table, $columns, $actualParams); + + if ($replaceQuotes) { + $expectedSQL = $this->replaceQuotes($expectedSQL); + } + $this->assertSame($expectedSQL, $actualSQL); $this->assertSame($expectedParams, $actualParams); } @@ -2036,7 +2040,7 @@ public function testInitFixtures(): void $this->assertInstanceOf('yii\db\QueryBuilder', $this->getQueryBuilder(true, true)); } - public function upsertProvider() + public static function upsertProvider(): array { return [ 'regular values' => [ @@ -2263,50 +2267,53 @@ public function testUpsert($table, $insertColumns, $updateColumns, $expectedSQL, } } - public function batchInsertProvider() + public static function batchInsertProvider(): array { return [ [ 'customer', ['email', 'name', 'address'], [['test@example.com', 'silverfire', 'Kyiv {{city}}, Ukraine']], - $this->replaceQuotes("INSERT INTO [[customer]] ([[email]], [[name]], [[address]]) VALUES ('test@example.com', 'silverfire', 'Kyiv {{city}}, Ukraine')"), + "INSERT INTO [[customer]] ([[email]], [[name]], [[address]]) VALUES ('test@example.com', 'silverfire', 'Kyiv {{city}}, Ukraine')", ], 'escape-danger-chars' => [ 'customer', ['address'], [["SQL-danger chars are escaped: '); --"]], - 'expected' => $this->replaceQuotes("INSERT INTO [[customer]] ([[address]]) VALUES ('SQL-danger chars are escaped: \'); --')"), + 'expected' => "INSERT INTO [[customer]] ([[address]]) VALUES ('SQL-danger chars are escaped: \'); --')", ], [ 'customer', ['address'], [], '', + false, ], [ 'customer', [], [['no columns passed']], - $this->replaceQuotes("INSERT INTO [[customer]] () VALUES ('no columns passed')"), + "INSERT INTO [[customer]] () VALUES ('no columns passed')", ], 'bool-false, bool2-null' => [ 'type', ['bool_col', 'bool_col2'], [[false, null]], - 'expected' => $this->replaceQuotes('INSERT INTO [[type]] ([[bool_col]], [[bool_col2]]) VALUES (0, NULL)'), + 'expected' => 'INSERT INTO [[type]] ([[bool_col]], [[bool_col2]]) VALUES (0, NULL)', ], [ '{{%type}}', ['{{%type}}.[[float_col]]', '[[time]]'], [[null, new Expression('now()')]], 'INSERT INTO {{%type}} ({{%type}}.[[float_col]], [[time]]) VALUES (NULL, now())', + false, ], 'bool-false, time-now()' => [ '{{%type}}', ['{{%type}}.[[bool_col]]', '[[time]]'], [[false, new Expression('now()')]], 'expected' => 'INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (0, now())', + false, ], ]; } @@ -2317,17 +2324,23 @@ public function batchInsertProvider() * @param array $columns * @param array $value * @param string $expected + * @param bool $replaceQuotes * @throws Exception */ - public function testBatchInsert($table, $columns, $value, $expected): void + public function testBatchInsert($table, $columns, $value, $expected, $replaceQuotes = true): void { $queryBuilder = $this->getQueryBuilder(); $sql = $queryBuilder->batchInsert($table, $columns, $value); + + if ($replaceQuotes) { + $expected = $this->replaceQuotes($expected); + } + $this->assertEquals($expected, $sql); } - public function updateProvider() + public static function updateProvider(): array { return [ [ @@ -2339,7 +2352,7 @@ public function updateProvider() [ 'id' => 100, ], - $this->replaceQuotes('UPDATE [[customer]] SET [[status]]=:qp0, [[updated_at]]=now() WHERE [[id]]=:qp1'), + 'UPDATE [[customer]] SET [[status]]=:qp0, [[updated_at]]=now() WHERE [[id]]=:qp1', [ ':qp0' => 1, ':qp1' => 100, @@ -2360,11 +2373,11 @@ public function testUpdate($table, $columns, $condition, $expectedSQL, $expected { $actualParams = []; $actualSQL = $this->getQueryBuilder()->update($table, $columns, $condition, $actualParams); - $this->assertSame($expectedSQL, $actualSQL); + $this->assertSame($this->replaceQuotes($expectedSQL), $actualSQL); $this->assertSame($expectedParams, $actualParams); } - public function deleteProvider() + public static function deleteProvider(): array { return [ [ @@ -2373,7 +2386,7 @@ public function deleteProvider() 'is_enabled' => false, 'power' => new Expression('WRONG_POWER()'), ], - $this->replaceQuotes('DELETE FROM [[user]] WHERE ([[is_enabled]]=:qp0) AND ([[power]]=WRONG_POWER())'), + 'DELETE FROM [[user]] WHERE ([[is_enabled]]=:qp0) AND ([[power]]=WRONG_POWER())', [ ':qp0' => false, ], @@ -2392,7 +2405,7 @@ public function testDelete($table, $condition, $expectedSQL, $expectedParams): v { $actualParams = []; $actualSQL = $this->getQueryBuilder()->delete($table, $condition, $actualParams); - $this->assertSame($expectedSQL, $actualSQL); + $this->assertSame($this->replaceQuotes($expectedSQL), $actualSQL); $this->assertSame($expectedParams, $actualParams); } @@ -2426,9 +2439,9 @@ public function testCommentTable(): void $this->assertEquals($this->replaceQuotes($expected), $sql); } - public function likeConditionProvider() + public static function likeConditionProvider(): array { - $conditions = [ + return [ // simple like [['like', 'name', 'foo%'], '[[name]] LIKE :qp0', [':qp0' => '%foo\%%']], [['not like', 'name', 'foo%'], '[[name]] NOT LIKE :qp0', [':qp0' => '%foo\%%']], @@ -2470,22 +2483,6 @@ public function likeConditionProvider() // like with expression as columnName [['like', new Expression('name'), 'teststring'], 'name LIKE :qp0', [':qp0' => '%teststring%']], ]; - - // adjust dbms specific escaping - foreach ($conditions as $i => $condition) { - $conditions[$i][1] = $this->replaceQuotes($condition[1]); - if (!empty($this->likeEscapeCharSql)) { - preg_match_all('/(?PLIKE.+?)( AND| OR|$)/', $conditions[$i][1], $matches, PREG_SET_ORDER); - foreach ($matches as $match) { - $conditions[$i][1] = str_replace($match['condition'], $match['condition'] . $this->likeEscapeCharSql, $conditions[$i][1]); - } - } - foreach ($conditions[$i][2] as $name => $value) { - $conditions[$i][2][$name] = strtr($conditions[$i][2][$name], $this->likeParameterReplacements); - } - } - - return $conditions; } /** @@ -2496,8 +2493,28 @@ public function likeConditionProvider() */ public function testBuildLikeCondition($condition, $expected, $expectedParams): void { + $expected = $this->replaceQuotes($expected); + + if (!empty($this->likeEscapeCharSql)) { + preg_match_all( + '/(?PLIKE.+?)( AND| OR|$)/', + $expected, + $matches, + PREG_SET_ORDER, + ); + + foreach ($matches as $match) { + $expected = str_replace($match['condition'], $match['condition'] . $this->likeEscapeCharSql, $expected); + } + } + + foreach ($expectedParams as $name => $value) { + $expectedParams[$name] = strtr($expectedParams[$name], $this->likeParameterReplacements); + } + $query = (new Query())->where($condition); list($sql, $params) = $this->getQueryBuilder()->build($query); + $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $this->replaceQuotes($expected)), $sql); $this->assertEquals($expectedParams, $params); } diff --git a/tests/framework/db/cubrid/QueryBuilderTest.php b/tests/framework/db/cubrid/QueryBuilderTest.php index ee0a30476bb..d3ac4e47949 100644 --- a/tests/framework/db/cubrid/QueryBuilderTest.php +++ b/tests/framework/db/cubrid/QueryBuilderTest.php @@ -8,7 +8,9 @@ namespace yiiunit\framework\db\cubrid; +use Closure; use PDO; +use yii\base\NotSupportedException; /** * @group db @@ -35,16 +37,6 @@ public function columnTypes() return array_merge(parent::columnTypes(), []); } - public function checksProvider(): void - { - $this->markTestSkipped('Adding/dropping check constraints is not supported in CUBRID.'); - } - - public function defaultValuesProvider(): void - { - $this->markTestSkipped('Adding/dropping default constraints is not supported in CUBRID.'); - } - public function testResetSequence(): void { $qb = $this->getQueryBuilder(); @@ -69,7 +61,7 @@ public function testCommentColumn(): void parent::testCommentColumn(); } - public function upsertProvider() + public static function upsertProvider(): array { $concreteData = [ 'regular values' => [ @@ -116,4 +108,32 @@ public function upsertProvider() return $newData; } + + /** + * @dataProvider checksProvider + * @param string $sql + */ + public function testAddDropCheck($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^.*::(addCheck|dropCheck) is not supported by CUBRID.*$/', + ); + + parent::testAddDropCheck($sql, $builder); + } + + /** + * @dataProvider defaultValuesProvider + * @param string $sql + */ + public function testAddDropDefaultValue($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^cubrid does not support (adding|dropping) default value constraints\.$/', + ); + + parent::testAddDropDefaultValue($sql, $builder); + } } diff --git a/tests/framework/db/mssql/QueryBuilderTest.php b/tests/framework/db/mssql/QueryBuilderTest.php index d07bd816b92..3d626dacd58 100644 --- a/tests/framework/db/mssql/QueryBuilderTest.php +++ b/tests/framework/db/mssql/QueryBuilderTest.php @@ -11,7 +11,6 @@ use yii\db\Expression; use yii\db\Query; use yiiunit\data\base\TraversableObject; -use yiiunit\framework\support\DbHelper; /** * @group db @@ -254,7 +253,7 @@ public function columnTypes() return array_merge(parent::columnTypes(), []); } - public function batchInsertProvider() + public static function batchInsertProvider(): array { $data = parent::batchInsertProvider(); @@ -265,7 +264,7 @@ public function batchInsertProvider() return $data; } - public function insertProvider() + public static function insertProvider(): array { return [ 'regular-values' => [ @@ -278,9 +277,9 @@ public function insertProvider() 'related_id' => null, ], [], - $this->replaceQuotes('SET NOCOUNT ON;DECLARE @temporary_inserted TABLE ([id] int , [email] varchar(128) , [name] varchar(128) NULL, [address] text NULL, [status] int NULL, [profile_id] int NULL);' . + 'SET NOCOUNT ON;DECLARE @temporary_inserted TABLE ([id] int , [email] varchar(128) , [name] varchar(128) NULL, [address] text NULL, [status] int NULL, [profile_id] int NULL);' . 'INSERT INTO [customer] ([email], [name], [address], [is_active], [related_id]) OUTPUT INSERTED.[id],INSERTED.[email],INSERTED.[name],INSERTED.[address],INSERTED.[status],INSERTED.[profile_id] INTO @temporary_inserted VALUES (:qp0, :qp1, :qp2, :qp3, :qp4);' . - 'SELECT * FROM @temporary_inserted'), + 'SELECT * FROM @temporary_inserted', [ ':qp0' => 'test@example.com', ':qp1' => 'silverfire', @@ -302,6 +301,7 @@ public function insertProvider() [ ':qp0' => null, ], + false, ], 'carry passed params' => [ 'customer', @@ -314,9 +314,9 @@ public function insertProvider() 'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']), ], [':phBar' => 'bar'], - $this->replaceQuotes('SET NOCOUNT ON;DECLARE @temporary_inserted TABLE ([id] int , [email] varchar(128) , [name] varchar(128) NULL, [address] text NULL, [status] int NULL, [profile_id] int NULL);' . + 'SET NOCOUNT ON;DECLARE @temporary_inserted TABLE ([id] int , [email] varchar(128) , [name] varchar(128) NULL, [address] text NULL, [status] int NULL, [profile_id] int NULL);' . 'INSERT INTO [customer] ([email], [name], [address], [is_active], [related_id], [col]) OUTPUT INSERTED.[id],INSERTED.[email],INSERTED.[name],INSERTED.[address],INSERTED.[status],INSERTED.[profile_id] INTO @temporary_inserted VALUES (:qp1, :qp2, :qp3, :qp4, :qp5, CONCAT(:phFoo, :phBar));' . - 'SELECT * FROM @temporary_inserted'), + 'SELECT * FROM @temporary_inserted', [ ':phBar' => 'bar', ':qp1' => 'test@example.com', @@ -347,9 +347,9 @@ public function insertProvider() 'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']), ]), [':phBar' => 'bar'], - $this->replaceQuotes('SET NOCOUNT ON;DECLARE @temporary_inserted TABLE ([id] int , [email] varchar(128) , [name] varchar(128) NULL, [address] text NULL, [status] int NULL, [profile_id] int NULL);' . + 'SET NOCOUNT ON;DECLARE @temporary_inserted TABLE ([id] int , [email] varchar(128) , [name] varchar(128) NULL, [address] text NULL, [status] int NULL, [profile_id] int NULL);' . 'INSERT INTO [customer] ([email], [name], [address], [is_active], [related_id]) OUTPUT INSERTED.[id],INSERTED.[email],INSERTED.[name],INSERTED.[address],INSERTED.[status],INSERTED.[profile_id] INTO @temporary_inserted SELECT [email], [name], [address], [is_active], [related_id] FROM [customer] WHERE ([email]=:qp1) AND ([name]=:qp2) AND ([address]=:qp3) AND ([is_active]=:qp4) AND ([related_id] IS NULL) AND ([col]=CONCAT(:phFoo, :phBar));' . - 'SELECT * FROM @temporary_inserted'), + 'SELECT * FROM @temporary_inserted', [ ':phBar' => 'bar', ':qp1' => 'test@example.com', @@ -375,7 +375,7 @@ public function testResetSequence(): void $this->assertEquals($expected, $sql); } - public function upsertProvider() + public static function upsertProvider(): array { $concreteData = [ 'regular values' => [ @@ -430,7 +430,7 @@ public function upsertProvider() public static function conditionProvider(): array { - $data = array_merge( + return array_merge( parent::conditionProvider(), [ [ @@ -462,28 +462,26 @@ public static function conditionProvider(): array ], //[ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id AND a.]]name[[ = ]]name`)', [':qp0' => 1] ], //[ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id]] AND a.[[name = ]]name`)', [':qp0' => 1] ], + 'composite in' => [ + [ + 'in', + ['id', 'name'], + [['id' => 1, 'name' => 'oy']], + ], + '(([id] = :qp0 AND [name] = :qp1))', + [':qp0' => 1, ':qp1' => 'oy'], + ], + 'composite in using array objects' => [ + [ + 'in', + new TraversableObject(['id', 'name']), + new TraversableObject([['id' => 1, 'name' => 'oy'], ['id' => 2, 'name' => 'yo']]) + ], + '(([id] = :qp0 AND [name] = :qp1) OR ([id] = :qp2 AND [name] = :qp3))', + [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'], + ], ], ); - $data['composite in'] = [ - ['in', ['id', 'name'], [['id' => 1, 'name' => 'oy']]], - '(([id] = :qp0 AND [name] = :qp1))', - [':qp0' => 1, ':qp1' => 'oy'], - ]; - $data['composite in using array objects'] = [ - ['in', new TraversableObject(['id', 'name']), new TraversableObject([ - ['id' => 1, 'name' => 'oy'], - ['id' => 2, 'name' => 'yo'], - ])], - '(([id] = :qp0 AND [name] = :qp1) OR ([id] = :qp2 AND [name] = :qp3))', - [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'], - ]; - - // adjust dbms specific escaping - foreach ($data as $i => $condition) { - $data[$i][1] = DbHelper::replaceQuotes($condition[1], 'sqlsrv'); - } - - return $data; } public function testAlterColumn(): void @@ -845,7 +843,7 @@ public function testDropColumnOnDb(): void $this->assertEquals(null, $schema->getColumn('bar')); } - public function buildFromDataProvider() + public static function buildFromDataProvider(): array { $data = parent::buildFromDataProvider(); $data[] = ['[test]', '[[test]]']; diff --git a/tests/framework/db/mysql/QueryBuilderTest.php b/tests/framework/db/mysql/QueryBuilderTest.php index 73352a808ed..92c22376ae8 100644 --- a/tests/framework/db/mysql/QueryBuilderTest.php +++ b/tests/framework/db/mysql/QueryBuilderTest.php @@ -8,13 +8,14 @@ namespace yiiunit\framework\db\mysql; +use Closure; use PDO; use yii\base\DynamicModel; +use yii\base\NotSupportedException; use yii\db\Expression; use yii\db\JsonExpression; use yii\db\Query; use yii\db\Schema; -use yiiunit\framework\support\DbHelper; /** * @group db @@ -166,7 +167,7 @@ public function columnTimeTypes() return $columns; } - public function primaryKeysProvider() + public static function primaryKeysProvider(): array { $result = parent::primaryKeysProvider(); $result['drop'][0] = 'ALTER TABLE {{T_constraints_1}} DROP PRIMARY KEY'; @@ -175,14 +176,14 @@ public function primaryKeysProvider() return $result; } - public function foreignKeysProvider() + public static function foreignKeysProvider(): array { $result = parent::foreignKeysProvider(); $result['drop'][0] = 'ALTER TABLE {{T_constraints_3}} DROP FOREIGN KEY [[CN_constraints_3]]'; return $result; } - public function indexesProvider() + public static function indexesProvider(): array { $result = parent::indexesProvider(); $result['create'][0] = 'ALTER TABLE {{T_constraints_2}} ADD INDEX [[CN_constraints_2_single]] ([[C_index_1]])'; @@ -192,18 +193,13 @@ public function indexesProvider() return $result; } - public function uniquesProvider() + public static function uniquesProvider(): array { $result = parent::uniquesProvider(); $result['drop'][0] = 'DROP INDEX [[CN_unique]] ON {{T_constraints_1}}'; return $result; } - public function defaultValuesProvider(): void - { - $this->markTestSkipped('Adding/dropping default constraints is not supported in MySQL.'); - } - public function testResetSequence(): void { $qb = $this->getQueryBuilder(); @@ -217,7 +213,7 @@ public function testResetSequence(): void $this->assertEquals($expected, $sql); } - public function upsertProvider() + public static function upsertProvider(): array { $concreteData = [ 'regular values' => [ @@ -266,7 +262,7 @@ public function upsertProvider() public static function conditionProvider(): array { - $data = array_merge( + return array_merge( parent::conditionProvider(), [ [ @@ -365,16 +361,9 @@ public static function conditionProvider(): array ] ], ); - - // adjust dbms specific escaping - foreach ($data as $i => $condition) { - $data[$i][1] = DbHelper::replaceQuotes($condition[1], 'mysql'); - } - - return $data; } - public function updateProvider() + public static function updateProvider(): array { $items = parent::updateProvider(); @@ -386,7 +375,7 @@ public function updateProvider() [ 'id' => 1, ], - $this->replaceQuotes('UPDATE [[profile]] SET [[description]]=:qp0 WHERE [[id]]=:qp1'), + 'UPDATE [[profile]] SET [[description]]=:qp0 WHERE [[id]]=:qp1', [ ':qp0' => '{"abc":"def","0":123,"1":null}', ':qp1' => 1, @@ -461,4 +450,18 @@ public function testDefaultValues(): void $sql = $command->insert('negative_default_values', [])->getRawSql(); $this->assertEquals('INSERT INTO `negative_default_values` (`tinyint_col`) VALUES (DEFAULT)', $sql); } + + /** + * @dataProvider defaultValuesProvider + * @param string $sql + */ + public function testAddDropDefaultValue($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^mysql does not support (adding|dropping) default value constraints\.$/', + ); + + parent::testAddDropDefaultValue($sql, $builder); + } } diff --git a/tests/framework/db/oci/QueryBuilderTest.php b/tests/framework/db/oci/QueryBuilderTest.php index c811a064522..ff51a6bbd6f 100644 --- a/tests/framework/db/oci/QueryBuilderTest.php +++ b/tests/framework/db/oci/QueryBuilderTest.php @@ -17,7 +17,6 @@ use yii\db\Query; use yii\helpers\ArrayHelper; use yiiunit\data\base\TraversableObject; -use yiiunit\framework\support\DbHelper; /** * @group db @@ -49,7 +48,7 @@ public function columnTypes() ]); } - public function foreignKeysProvider() + public static function foreignKeysProvider(): array { $tableName = 'T_constraints_3'; $name = 'CN_constraints_3'; @@ -76,22 +75,13 @@ function (QueryBuilder $qb) use ($tableName, $name, $pkTableName) { ]; } - public function indexesProvider() + public static function indexesProvider(): array { $result = parent::indexesProvider(); $result['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]'; return $result; } - /** - * @dataProvider defaultValuesProvider - * @param string $sql - */ - public function testAddDropDefaultValue($sql, Closure $builder): void - { - $this->markTestSkipped('Adding/dropping default constraints is not supported in Oracle.'); - } - public function testCommentColumn(): void { $qb = $this->getQueryBuilder(); @@ -133,25 +123,9 @@ public function testExecuteResetSequence(): void $this->assertEquals(4, $result); } - public function likeConditionProvider() - { - /* - * Different pdo_oci8 versions may or may not implement PDO::quote(), so - * yii\db\Schema::quoteValue() may or may not quote \. - */ - try { - $encodedBackslash = substr($this->getDb()->quoteValue('\\'), 1, -1); - $this->likeParameterReplacements[$encodedBackslash] = '\\'; - } catch (Exception $e) { - $this->markTestSkipped('Could not execute Connection::quoteValue() method: ' . $e->getMessage()); - } - - return parent::likeConditionProvider(); - } - public static function conditionProvider(): array { - $data = array_merge( + return array_merge( parent::conditionProvider(), [ [ @@ -201,13 +175,6 @@ public static function conditionProvider(): array ], ], ); - - // adjust dbms specific escaping - foreach ($data as $i => $condition) { - $data[$i][1] = DbHelper::replaceQuotes($condition[1], 'oci'); - } - - return $data; } public function conditionProvidertmp() @@ -262,7 +229,7 @@ protected function generateSprintfSeries($pattern, $from, $to) return $items; } - public function upsertProvider() + public static function upsertProvider(): array { $concreteData = [ 'regular values' => [ @@ -322,7 +289,7 @@ public function upsertProvider() return $newData; } - public function batchInsertProvider() + public static function batchInsertProvider(): array { $data = parent::batchInsertProvider(); @@ -368,17 +335,42 @@ public function testInitFixtures(): void */ public function testUpsert($table, $insertColumns, $updateColumns, $expectedSQL, $expectedParams): void { - $actualParams = []; - $actualSQL = $this->getQueryBuilder(true, $this->driverName === 'sqlite')->upsert($table, $insertColumns, $updateColumns, $actualParams); - if (is_string($expectedSQL)) { - $this->assertEqualsWithoutLE($expectedSQL, $actualSQL); - } else { - $this->assertStringContainsString($actualSQL, $expectedSQL); - } - if (ArrayHelper::isAssociative($expectedParams)) { - $this->assertSame($expectedParams, $actualParams); - } else { - $this->assertIsOneOf($actualParams, $expectedParams); + parent::testUpsert($table, $insertColumns, $updateColumns, $expectedSQL, $expectedParams); + } + + /** + * @dataProvider defaultValuesProvider + * @param string $sql + */ + public function testAddDropDefaultValue($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^oci does not support (adding|dropping) default value constraints\.$/', + ); + + parent::testAddDropDefaultValue($sql, $builder); + } + + /** + * @dataProvider likeConditionProvider + * @param array $condition + * @param string $expected + * @param array $expectedParams + */ + public function testBuildLikeCondition($condition, $expected, $expectedParams): void + { + /** + * Different pdo_oci8 versions may or may not implement PDO::quote(), so + * yii\db\Schema::quoteValue() may or may not quote \. + */ + try { + $encodedBackslash = substr($this->getDb()->quoteValue('\\\\'), 1, -1); + $this->likeParameterReplacements[$encodedBackslash] = '\\'; + } catch (Exception $e) { + $this->markTestSkipped('Could not execute Connection::quoteValue() method: ' . $e->getMessage()); } + + parent::testBuildLikeCondition($condition, $expected, $expectedParams); } } diff --git a/tests/framework/db/pgsql/QueryBuilderTest.php b/tests/framework/db/pgsql/QueryBuilderTest.php index d4296563209..01bfbe190ca 100644 --- a/tests/framework/db/pgsql/QueryBuilderTest.php +++ b/tests/framework/db/pgsql/QueryBuilderTest.php @@ -8,14 +8,15 @@ namespace yiiunit\framework\db\pgsql; +use Closure; use yii\base\DynamicModel; +use yii\base\NotSupportedException; use yii\db\ArrayExpression; use yii\db\Expression; use yii\db\JsonExpression; use yii\db\Query; use yii\db\Schema; use yiiunit\data\base\TraversableObject; -use yiiunit\framework\support\DbHelper; /** * @group db @@ -70,7 +71,7 @@ public function columnTypes() public static function conditionProvider(): array { - $data = array_merge( + return array_merge( parent::conditionProvider(), [ [ @@ -202,13 +203,6 @@ public static function conditionProvider(): array [['&&', 'id', new ArrayExpression([1])], '"id" && ARRAY[:qp0]', [':qp0' => 1]], ], ); - - // adjust dbms specific escaping - foreach ($data as $i => $condition) { - $data[$i][1] = DbHelper::replaceQuotes($condition[1], 'pgsql'); - } - - return $data; } public function testAlterColumn(): void @@ -260,18 +254,13 @@ public function testAlterColumn(): void $this->assertEquals($expected, $sql); } - public function indexesProvider() + public static function indexesProvider(): array { $result = parent::indexesProvider(); $result['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]'; return $result; } - public function defaultValuesProvider(): void - { - $this->markTestSkipped('Adding/dropping default constraints is not supported in PostgreSQL.'); - } - public function testCommentColumn(): void { $qb = $this->getQueryBuilder(); @@ -298,7 +287,7 @@ public function testCommentTable(): void $this->assertEquals($this->replaceQuotes($expected), $sql); } - public function batchInsertProvider() + public static function batchInsertProvider(): array { $data = parent::batchInsertProvider(); @@ -343,7 +332,7 @@ public function testResetSequencePostgres12(): void $this->assertEquals($expected, $sql); } - public function upsertProvider() + public static function upsertProvider(): array { $concreteData = [ 'regular values' => [ @@ -443,7 +432,7 @@ public function upsertProvider() return $newData; } - public function updateProvider() + public static function updateProvider(): array { $items = parent::updateProvider(); @@ -455,7 +444,7 @@ public function updateProvider() [ 'id' => 1, ], - $this->replaceQuotes('UPDATE [[profile]] SET [[description]]=:qp0 WHERE [[id]]=:qp1'), + 'UPDATE [[profile]] SET [[description]]=:qp0 WHERE [[id]]=:qp1', [ ':qp0' => '{"abc":"def","0":123,"1":null}', ':qp1' => 1, @@ -489,4 +478,18 @@ public function testDropIndex(): void $sql = $qb->dropIndex('index', '{{%schema.table}}'); $this->assertEquals($expected, $sql); } + + /** + * @dataProvider defaultValuesProvider + * @param string $sql + */ + public function testAddDropDefaultValue($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^pgsql does not support (adding|dropping) default value constraints\.$/', + ); + + parent::testAddDropDefaultValue($sql, $builder); + } } diff --git a/tests/framework/db/sqlite/QueryBuilderTest.php b/tests/framework/db/sqlite/QueryBuilderTest.php index 5b12b4f0f96..c42c9be130f 100644 --- a/tests/framework/db/sqlite/QueryBuilderTest.php +++ b/tests/framework/db/sqlite/QueryBuilderTest.php @@ -8,13 +8,14 @@ namespace yiiunit\framework\db\sqlite; +use Closure; use PDO; +use yii\base\NotSupportedException; use yii\db\Expression; use yii\db\Query; use yii\db\Schema; use yii\db\sqlite\QueryBuilder; use yiiunit\data\base\TraversableObject; -use yiiunit\framework\support\DbHelper; /** * @group db @@ -39,7 +40,7 @@ public function columnTypes() public static function conditionProvider(): array { - $data = array_merge( + return array_merge( parent::conditionProvider(), [ [ @@ -71,46 +72,29 @@ public static function conditionProvider(): array ], //[ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id AND a.]]name[[ = ]]name`)', [':qp0' => 1] ], //[ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id]] AND a.[[name = ]]name`)', [':qp0' => 1] ], + 'composite in' => [ + [ + 'in', + ['id', 'name'], + [['id' => 1, 'name' => 'oy']], + ], + '(([[id]] = :qp0 AND [[name]] = :qp1))', + [':qp0' => 1, ':qp1' => 'oy'], + ], + 'composite in using array objects' => [ + [ + 'in', + new TraversableObject(['id', 'name']), + new TraversableObject([['id' => 1, 'name' => 'oy'], ['id' => 2, 'name' => 'yo']]), + ], + '(([[id]] = :qp0 AND [[name]] = :qp1) OR ([[id]] = :qp2 AND [[name]] = :qp3))', + [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'], + ], ], ); - $data['composite in'] = [ - [ - 'in', - ['id', 'name'], - [['id' => 1, 'name' => 'oy']], - ], - '(([[id]] = :qp0 AND [[name]] = :qp1))', - [':qp0' => 1, ':qp1' => 'oy'], - ]; - $data['composite in using array objects'] = [ - [ - 'in', - new TraversableObject(['id', 'name']), - new TraversableObject([['id' => 1, 'name' => 'oy'], ['id' => 2, 'name' => 'yo']]), - ], - '(([[id]] = :qp0 AND [[name]] = :qp1) OR ([[id]] = :qp2 AND [[name]] = :qp3))', - [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'], - ]; - - // adjust dbms specific escaping - foreach ($data as $i => $condition) { - $data[$i][1] = DbHelper::replaceQuotes($condition[1], 'sqlite'); - } - - return $data; - } - - public function primaryKeysProvider(): void - { - $this->markTestSkipped('Adding/dropping primary keys is not supported in SQLite.'); } - public function foreignKeysProvider(): void - { - $this->markTestSkipped('Adding/dropping foreign keys is not supported in SQLite.'); - } - - public function indexesProvider() + public static function indexesProvider(): array { $result = parent::indexesProvider(); $result['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]'; @@ -129,21 +113,6 @@ function (QueryBuilder $qb) use ($tableName, $indexName, $schemaName) { return $result; } - public function uniquesProvider(): void - { - $this->markTestSkipped('Adding/dropping unique constraints is not supported in SQLite.'); - } - - public function checksProvider(): void - { - $this->markTestSkipped('Adding/dropping check constraints is not supported in SQLite.'); - } - - public function defaultValuesProvider(): void - { - $this->markTestSkipped('Adding/dropping default constraints is not supported in SQLite.'); - } - public function testCommentColumn(): void { $this->markTestSkipped('Comments are not supported in SQLite'); @@ -154,7 +123,7 @@ public function testCommentTable(): void $this->markTestSkipped('Comments are not supported in SQLite'); } - public function batchInsertProvider() + public static function batchInsertProvider(): array { $data = parent::batchInsertProvider(); $data['escape-danger-chars']['expected'] = "INSERT INTO `customer` (`address`) VALUES ('SQL-danger chars are escaped: ''); --')"; @@ -248,7 +217,7 @@ public function testResetSequence(): void $this->assertEquals($expected, $sql); } - public function upsertProvider() + public static function upsertProvider(): array { $concreteData = [ 'regular values' => [ @@ -294,4 +263,74 @@ public function upsertProvider() } return $newData; } + + /** + * @dataProvider primaryKeysProvider + * @param string $sql + */ + public function testAddDropPrimaryKey($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^.*::(addPrimaryKey|dropPrimaryKey) is not supported by SQLite\.$/', + ); + + parent::testAddDropPrimaryKey($sql, $builder); + } + + /** + * @dataProvider foreignKeysProvider + * @param string $sql + */ + public function testAddDropForeignKey($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^.*::(addForeignKey|dropForeignKey) is not supported by SQLite\.$/', + ); + + parent::testAddDropForeignKey($sql, $builder); + } + + /** + * @dataProvider uniquesProvider + * @param string $sql + */ + public function testAddDropUnique($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^.*::(addUnique|dropUnique) is not supported by SQLite\.$/', + ); + + parent::testAddDropUnique($sql, $builder); + } + + /** + * @dataProvider checksProvider + * @param string $sql + */ + public function testAddDropCheck($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^.*::(addCheck|dropCheck) is not supported by SQLite\.$/', + ); + + parent::testAddDropCheck($sql, $builder); + } + + /** + * @dataProvider defaultValuesProvider + * @param string $sql + */ + public function testAddDropDefaultValue($sql, Closure $builder): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessageMatches( + '/^.*::(addDefaultValue|dropDefaultValue) is not supported by SQLite\.$/', + ); + + parent::testAddDropDefaultValue($sql, $builder); + } } diff --git a/tests/framework/support/DbHelper.php b/tests/framework/support/DbHelper.php deleted file mode 100644 index ac06b13753c..00000000000 --- a/tests/framework/support/DbHelper.php +++ /dev/null @@ -1,43 +0,0 @@ - Date: Wed, 22 Oct 2025 13:15:29 -0300 Subject: [PATCH 2/7] Apply fixed Copilot review. --- tests/framework/db/QueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 117d9db8fe7..7556987e907 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -2017,7 +2017,7 @@ public static function insertProvider(): array * @param array $params * @param string $expectedSQL * @param array $expectedParams - * @param array $replaceQuotes + * @param bool $replaceQuotes */ public function testInsert($table, $columns, $params, $expectedSQL, $expectedParams, $replaceQuotes = true): void { From 161192831cea84eb309512c0dbfd173894780046 Mon Sep 17 00:00:00 2001 From: Maksim Spirkov <63721828+mspirkov@users.noreply.github.com> Date: Sat, 25 Oct 2025 22:12:38 +0300 Subject: [PATCH 3/7] Fix #20636: Fix `@param` annotations for `$message` in logging methods --- framework/BaseYii.php | 6 +++--- framework/CHANGELOG.md | 1 + framework/log/Logger.php | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/framework/BaseYii.php b/framework/BaseYii.php index cb7ac56e8e5..506b78fafff 100644 --- a/framework/BaseYii.php +++ b/framework/BaseYii.php @@ -433,7 +433,7 @@ public static function trace($message, $category = 'application') * Logs an error message. * An error message is typically logged when an unrecoverable error occurs * during the execution of an application. - * @param string|array $message the message to be logged. This can be a simple string or a more + * @param string|array|\Throwable $message the message to be logged. This can be a simple string or a more * complex data structure, such as an array. * @param string $category the category of the message. */ @@ -446,7 +446,7 @@ public static function error($message, $category = 'application') * Logs a warning message. * A warning message is typically logged when an error occurs while the execution * can still continue. - * @param string|array $message the message to be logged. This can be a simple string or a more + * @param string|array|\Throwable $message the message to be logged. This can be a simple string or a more * complex data structure, such as an array. * @param string $category the category of the message. */ @@ -459,7 +459,7 @@ public static function warning($message, $category = 'application') * Logs an informative message. * An informative message is typically logged by an application to keep record of * something important (e.g. an administrator logs in). - * @param string|array $message the message to be logged. This can be a simple string or a more + * @param string|array|\Throwable $message the message to be logged. This can be a simple string or a more * complex data structure, such as an array. * @param string $category the category of the message. */ diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 9e7b07c8318..363e48604b1 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -67,6 +67,7 @@ Yii Framework 2 Change Log - Bug #20617: Fix `@return` annotation for `DataColumn::getDataCellValue()` (mspirkov) - Bug #20628: Fix `@return` annotations for `lastInsertId` methods in `yii\db\mssql` namespace (mspirkov) - Bug #20630: Fix `@var` annotations for `yii\web\CompositeUrlRule::$rules` and `yii\web\GroupUrlRule::$rules` (mspirkov) +- Bug #20636: Fix `@param` annotations for `$message` in logging methods (mspirkov) 2.0.53 June 27, 2025 diff --git a/framework/log/Logger.php b/framework/log/Logger.php index c2aa21f41cf..4671533b65f 100644 --- a/framework/log/Logger.php +++ b/framework/log/Logger.php @@ -144,7 +144,7 @@ public function init() * Logs a message with the given type and category. * If [[traceLevel]] is greater than 0, additional call stack information about * the application code will be logged as well. - * @param string|array $message the message to be logged. This can be a simple string or a more + * @param string|array|\Throwable $message the message to be logged. This can be a simple string or a more * complex data structure that will be handled by a [[Target|log target]]. * @param int $level the level of the message. This must be one of the following: * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, `Logger::LEVEL_PROFILE`, From 8b91c77389e14a5a9026a906ee21a3d1ed6a9af7 Mon Sep 17 00:00:00 2001 From: Maksim Spirkov <63721828+mspirkov@users.noreply.github.com> Date: Sat, 25 Oct 2025 22:19:58 +0300 Subject: [PATCH 4/7] Fix #20634: Fix PHPDoc annotations in `yii\db` namespace. Add PHPStan/Psalm annotations for `yii\db\SqlTokenizer::startsWithAnyLongest()` --- framework/CHANGELOG.md | 1 + framework/base/DynamicModel.php | 2 +- framework/db/ActiveRelationTrait.php | 2 +- framework/db/ColumnSchema.php | 2 +- framework/db/QueryBuilder.php | 1 + framework/db/SqlTokenizer.php | 6 +++++- framework/db/pgsql/ArrayExpressionBuilder.php | 2 +- 7 files changed, 11 insertions(+), 5 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 363e48604b1..7ebc26b218f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -67,6 +67,7 @@ Yii Framework 2 Change Log - Bug #20617: Fix `@return` annotation for `DataColumn::getDataCellValue()` (mspirkov) - Bug #20628: Fix `@return` annotations for `lastInsertId` methods in `yii\db\mssql` namespace (mspirkov) - Bug #20630: Fix `@var` annotations for `yii\web\CompositeUrlRule::$rules` and `yii\web\GroupUrlRule::$rules` (mspirkov) +- Bug #20634: Fix PHPDoc annotations in `yii\db` namespace. Add PHPStan/Psalm annotations for `yii\db\SqlTokenizer::startsWithAnyLongest()` (mspirkov) - Bug #20636: Fix `@param` annotations for `$message` in logging methods (mspirkov) diff --git a/framework/base/DynamicModel.php b/framework/base/DynamicModel.php index 219ddc916ca..a0f7bbf619d 100644 --- a/framework/base/DynamicModel.php +++ b/framework/base/DynamicModel.php @@ -223,7 +223,7 @@ public function addRule($attributes, $validator, $options = []) */ public static function validateData(array $data, $rules = []) { - /** @var self $model */ + /** @var static $model */ $model = new static($data); if (!empty($rules)) { $validators = $model->getValidators(); diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php index 53addd17ef4..2347098ff71 100644 --- a/framework/db/ActiveRelationTrait.php +++ b/framework/db/ActiveRelationTrait.php @@ -45,7 +45,7 @@ trait ActiveRelationTrait */ public $link; /** - * @var array|object the query associated with the junction table. Please call [[via()]] + * @var array|object|null the query associated with the junction table. Please call [[via()]] * to set this property instead of directly setting it. * This property is only used in relational context. * @see via() diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php index ef3c9b06d4d..4893d528fca 100644 --- a/framework/db/ColumnSchema.php +++ b/framework/db/ColumnSchema.php @@ -62,7 +62,7 @@ class ColumnSchema extends BaseObject */ public $scale; /** - * @var bool whether this column is a primary key + * @var bool|null whether this column is a primary key */ public $isPrimaryKey; /** diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index a83c2b3a14a..476ce3d41a0 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -319,6 +319,7 @@ public function getExpressionBuilder(ExpressionInterface $expression) } if ($this->expressionBuilders[$className] === __CLASS__) { + /** @phpstan-var $this&ExpressionBuilderInterface */ return $this; } diff --git a/framework/db/SqlTokenizer.php b/framework/db/SqlTokenizer.php index 63e53a9b4d2..6cba2f9bfe6 100644 --- a/framework/db/SqlTokenizer.php +++ b/framework/db/SqlTokenizer.php @@ -176,12 +176,15 @@ abstract protected function isKeyword($string, &$content); /** * Returns whether the longest common prefix equals to the SQL code of the same length at the current offset. - * @param string[] $with strings to be tested. + * @param array $with strings to be tested. * The method **will** modify this parameter to speed up lookups. * @param bool $caseSensitive whether to perform a case sensitive comparison. * @param int|null $length length of the matched string. * @param string|null $content matched string. * @return bool whether a match is found. + * + * @phpstan-param array|array> $with + * @psalm-param array|array> $with */ protected function startsWithAnyLongest(array &$with, $caseSensitive, &$length = null, &$content = null) { @@ -194,6 +197,7 @@ protected function startsWithAnyLongest(array &$with, $caseSensitive, &$length = return mb_strlen($string2, 'UTF-8') - mb_strlen($string1, 'UTF-8'); }); $map = []; + /** @var string $string */ foreach ($with as $string) { $map[mb_strlen($string, 'UTF-8')][$caseSensitive ? $string : mb_strtoupper($string, 'UTF-8')] = true; } diff --git a/framework/db/pgsql/ArrayExpressionBuilder.php b/framework/db/pgsql/ArrayExpressionBuilder.php index 9da7f257cc6..128a633ed58 100644 --- a/framework/db/pgsql/ArrayExpressionBuilder.php +++ b/framework/db/pgsql/ArrayExpressionBuilder.php @@ -132,7 +132,7 @@ protected function buildSubqueryArray($sql, ArrayExpression $expression) * * @param ArrayExpression $expression * @param mixed $value - * @return JsonExpression + * @return mixed */ protected function typecastValue(ArrayExpression $expression, $value) { From f5841ab98c63ceff074a72325d4e68c4dc7209bf Mon Sep 17 00:00:00 2001 From: Maksim Spirkov <63721828+mspirkov@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:53:46 +0300 Subject: [PATCH 5/7] Fix #20639: Add missing generics in `yii\web` namespace --- framework/BaseYii.php | 6 ++++++ framework/CHANGELOG.md | 1 + framework/Yii.php | 3 +++ framework/web/Application.php | 8 ++++++++ framework/web/CookieCollection.php | 8 ++++++++ framework/web/HeaderCollection.php | 9 +++++++++ framework/web/Session.php | 3 +++ framework/web/SessionIterator.php | 2 ++ 8 files changed, 40 insertions(+) diff --git a/framework/BaseYii.php b/framework/BaseYii.php index 506b78fafff..889874b301c 100644 --- a/framework/BaseYii.php +++ b/framework/BaseYii.php @@ -12,6 +12,7 @@ use yii\base\UnknownClassException; use yii\di\Container; use yii\log\Logger; +use yii\web\IdentityInterface; /** * Gets the application start timestamp. @@ -56,6 +57,8 @@ * * @author Qiang Xue * @since 2.0 + * + * @template TUserIdentity of IdentityInterface */ class BaseYii { @@ -69,6 +72,9 @@ class BaseYii public static $classMap = []; /** * @var \yii\console\Application|\yii\web\Application the application instance + * + * @phpstan-var \yii\console\Application|\yii\web\Application + * @psalm-var \yii\console\Application|\yii\web\Application */ public static $app; /** diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 7ebc26b218f..765685057e8 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -69,6 +69,7 @@ Yii Framework 2 Change Log - Bug #20630: Fix `@var` annotations for `yii\web\CompositeUrlRule::$rules` and `yii\web\GroupUrlRule::$rules` (mspirkov) - Bug #20634: Fix PHPDoc annotations in `yii\db` namespace. Add PHPStan/Psalm annotations for `yii\db\SqlTokenizer::startsWithAnyLongest()` (mspirkov) - Bug #20636: Fix `@param` annotations for `$message` in logging methods (mspirkov) +- Bug #20639: Add missing generics in `yii\web` namespace (mspirkov) 2.0.53 June 27, 2025 diff --git a/framework/Yii.php b/framework/Yii.php index ee9a1b5dc69..985e24dab75 100644 --- a/framework/Yii.php +++ b/framework/Yii.php @@ -18,6 +18,9 @@ * @since 2.0 * @phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace + * + * @template TUserIdentity of \yii\web\IdentityInterface + * @extends \yii\BaseYii */ class Yii extends \yii\BaseYii { diff --git a/framework/web/Application.php b/framework/web/Application.php index 5f9dcd0418c..2f68ee7d065 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -25,6 +25,11 @@ * * @author Qiang Xue * @since 2.0 + * + * @template TUserIdentity of IdentityInterface + * + * @phpstan-property-read User $user + * @psalm-property-read User $user */ class Application extends \yii\base\Application { @@ -181,6 +186,9 @@ public function getSession() /** * Returns the user component. * @return User the user component. + * + * @phpstan-return User + * @psalm-return User */ public function getUser() { diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php index c476c694c85..d4f355cc811 100644 --- a/framework/web/CookieCollection.php +++ b/framework/web/CookieCollection.php @@ -22,6 +22,12 @@ * * @author Qiang Xue * @since 2.0 + * + * @implements \IteratorAggregate + * @implements \ArrayAccess + * + * @phpstan-property-read ArrayIterator $iterator + * @psalm-property-read ArrayIterator $iterator */ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayAccess, \Countable { @@ -32,6 +38,8 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA /** * @var Cookie[] the cookies in this collection (indexed by the cookie names) + * + * @phpstan-var array */ private $_cookies; diff --git a/framework/web/HeaderCollection.php b/framework/web/HeaderCollection.php index 7c5e8efae81..dec4de40586 100644 --- a/framework/web/HeaderCollection.php +++ b/framework/web/HeaderCollection.php @@ -17,6 +17,12 @@ * * @author Qiang Xue * @since 2.0 + * + * @implements \IteratorAggregate + * @implements \ArrayAccess + * + * @phpstan-property-read \ArrayIterator $iterator + * @psalm-property-read \ArrayIterator $iterator */ class HeaderCollection extends BaseObject implements \IteratorAggregate, \ArrayAccess, \Countable { @@ -39,6 +45,9 @@ class HeaderCollection extends BaseObject implements \IteratorAggregate, \ArrayA * This method is required by the SPL interface [[\IteratorAggregate]]. * It will be implicitly called when you use `foreach` to traverse the collection. * @return \ArrayIterator an iterator for traversing the headers in the collection. + * + * @phpstan-return \ArrayIterator + * @psalm-return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() diff --git a/framework/web/Session.php b/framework/web/Session.php index 83cba19e5c5..037a2037899 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -70,6 +70,9 @@ * * @author Qiang Xue * @since 2.0 + * + * @implements \IteratorAggregate + * @implements \ArrayAccess */ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable { diff --git a/framework/web/SessionIterator.php b/framework/web/SessionIterator.php index e4b4c9eb809..6944bef1bdb 100644 --- a/framework/web/SessionIterator.php +++ b/framework/web/SessionIterator.php @@ -12,6 +12,8 @@ * * @author Qiang Xue * @since 2.0 + * + * @implements \Iterator */ class SessionIterator implements \Iterator { From 8b89b93787ae3f6a1494f98ed360ff192f445d46 Mon Sep 17 00:00:00 2001 From: Maksim Spirkov <63721828+mspirkov@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:56:00 +0300 Subject: [PATCH 6/7] Fix codestyle in `tests` (#20638) --- phpcs.xml.dist | 5 ---- tests/framework/base/ControllerTest.php | 7 +++++- tests/framework/db/ActiveQueryTest.php | 24 +++++++++---------- tests/framework/db/ActiveRecordTest.php | 2 +- .../framework/db/GetTablesAliasTestTrait.php | 20 ++++++++-------- tests/framework/di/InstanceTest.php | 8 +++---- tests/framework/grid/DataColumnTest.php | 10 ++++---- tests/framework/helpers/BaseConsoleTest.php | 8 +++---- tests/framework/helpers/HtmlTest.php | 16 ++++++------- tests/framework/helpers/StringHelperTest.php | 12 +++++----- .../validators/FileValidatorTest.php | 6 ++--- tests/framework/web/AssetBundleTest.php | 14 +++++------ 12 files changed, 66 insertions(+), 66 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 5ff7758c043..80d44a9e922 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -7,11 +7,6 @@ tests/* - - - tests/* - - tests/* diff --git a/tests/framework/base/ControllerTest.php b/tests/framework/base/ControllerTest.php index 0491239ec2f..3c07a84763b 100644 --- a/tests/framework/base/ControllerTest.php +++ b/tests/framework/base/ControllerTest.php @@ -110,7 +110,9 @@ public static function actionIdMethodProvider(): array } } - +/** + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ class TestController extends Controller { public function actionTest1() @@ -138,6 +140,9 @@ public function actionTest_test() } } +/** + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ class Test1Controller extends Controller { public function actionTest_1() diff --git a/tests/framework/db/ActiveQueryTest.php b/tests/framework/db/ActiveQueryTest.php index e1d38149749..3baa552c7b8 100644 --- a/tests/framework/db/ActiveQueryTest.php +++ b/tests/framework/db/ActiveQueryTest.php @@ -66,7 +66,7 @@ public function testPrepare(): void $this->assertInstanceOf('yii\db\Query', $result); } - public function testPopulate_EmptyRows(): void + public function testPopulateEmptyRows(): void { $query = new ActiveQuery(Customer::class); $rows = []; @@ -77,7 +77,7 @@ public function testPopulate_EmptyRows(): void /** * @todo tests for internal logic of populate() */ - public function testPopulate_FilledRows(): void + public function testPopulateFilledRows(): void { $query = new ActiveQuery(Customer::class); $rows = $query->all(); @@ -162,14 +162,14 @@ public function testBuildJoinWithRemoveDuplicateJoinByTableName(): void /** * @todo tests for the regex inside getQueryTableName */ - public function testGetQueryTableName_from_not_set(): void + public function testGetQueryTableNameFromNotSet(): void { $query = new ActiveQuery(Customer::class); $result = $this->invokeMethod($query, 'getTableNameAndAlias'); $this->assertEquals(['customer', 'customer'], $result); } - public function testGetQueryTableName_from_set(): void + public function testGetQueryTableNameFromSet(): void { $options = ['from' => ['alias' => 'customer']]; $query = new ActiveQuery(Customer::class, $options); @@ -187,7 +187,7 @@ public function testOnCondition(): void $this->assertEquals($params, $result->params); } - public function testAndOnCondition_on_not_set(): void + public function testAndOnConditionOnNotSet(): void { $query = new ActiveQuery(Customer::class); $on = ['active' => true]; @@ -197,7 +197,7 @@ public function testAndOnCondition_on_not_set(): void $this->assertEquals($params, $result->params); } - public function testAndOnCondition_on_set(): void + public function testAndOnConditionOnSet(): void { $onOld = ['active' => true]; $query = new ActiveQuery(Customer::class); @@ -210,7 +210,7 @@ public function testAndOnCondition_on_set(): void $this->assertEquals($params, $result->params); } - public function testOrOnCondition_on_not_set(): void + public function testOrOnConditionOnNotSet(): void { $query = new ActiveQuery(Customer::class); $on = ['active' => true]; @@ -220,7 +220,7 @@ public function testOrOnCondition_on_not_set(): void $this->assertEquals($params, $result->params); } - public function testOrOnCondition_on_set(): void + public function testOrOnConditionOnSet(): void { $onOld = ['active' => true]; $query = new ActiveQuery(Customer::class); @@ -244,7 +244,7 @@ public function testViaTable(): void $this->assertInstanceOf('yii\db\ActiveQuery', $result->via); } - public function testAlias_not_set(): void + public function testAliasNotSet(): void { $query = new ActiveQuery(Customer::class); $result = $query->alias('alias'); @@ -252,7 +252,7 @@ public function testAlias_not_set(): void $this->assertEquals(['alias' => 'customer'], $result->from); } - public function testAlias_yet_set(): void + public function testAliasYetSet(): void { $aliasOld = ['old']; $query = new ActiveQuery(Customer::class); @@ -267,7 +267,7 @@ protected function createQuery() return new ActiveQuery(null); } - public function testGetTableNames_notFilledFrom(): void + public function testGetTableNamesNotFilledFrom(): void { $query = new ActiveQuery(Profile::class); @@ -278,7 +278,7 @@ public function testGetTableNames_notFilledFrom(): void ], $tables); } - public function testGetTableNames_wontFillFrom(): void + public function testGetTableNamesWontFillFrom(): void { $query = new ActiveQuery(Profile::class); $this->assertEquals($query->from, null); diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index 232b87a9fe8..4427989a168 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -2064,7 +2064,7 @@ public function testCustomARRelation(): void $this->assertInstanceOf(Order::class, $orderItem->custom); } - public function testRefresh_querySetAlias_findRecord(): void + public function testRefreshQuerySetAliasFindRecord(): void { $customer = new CustomerWithAlias(); $customer->id = 1; diff --git a/tests/framework/db/GetTablesAliasTestTrait.php b/tests/framework/db/GetTablesAliasTestTrait.php index 831c97147e3..33450c53377 100644 --- a/tests/framework/db/GetTablesAliasTestTrait.php +++ b/tests/framework/db/GetTablesAliasTestTrait.php @@ -20,7 +20,7 @@ trait GetTablesAliasTestTrait */ abstract protected function createQuery(); - public function testGetTableNames_isFromArrayWithAlias(): void + public function testGetTableNamesIsFromArrayWithAlias(): void { $query = $this->createQuery(); $query->from = [ @@ -40,7 +40,7 @@ public function testGetTableNames_isFromArrayWithAlias(): void ], $tables); } - public function testGetTableNames_isFromArrayWithoutAlias(): void + public function testGetTableNamesIsFromArrayWithoutAlias(): void { $query = $this->createQuery(); $query->from = [ @@ -56,7 +56,7 @@ public function testGetTableNames_isFromArrayWithoutAlias(): void ], $tables); } - public function testGetTableNames_isFromString(): void + public function testGetTableNamesIsFromString(): void { $query = $this->createQuery(); $query->from = 'profile AS \'prf\', user "usr", `order`, "customer", "a b" as "c d"'; @@ -72,7 +72,7 @@ public function testGetTableNames_isFromString(): void ], $tables); } - public function testGetTableNames_isFromObject_generateException(): void + public function testGetTableNamesIsFromObjectGenerateException(): void { $query = $this->createQuery(); $query->from = new stdClass(); @@ -82,7 +82,7 @@ public function testGetTableNames_isFromObject_generateException(): void $query->getTablesUsedInFrom(); } - public function testGetTablesAlias_isFromString(): void + public function testGetTablesAliasIsFromString(): void { $query = $this->createQuery(); $query->from = 'profile AS \'prf\', user "usr", service srv, order, [a b] [c d], {{something}} AS myalias'; @@ -102,7 +102,7 @@ public function testGetTablesAlias_isFromString(): void /** * @see https://github.com/yiisoft/yii2/issues/14150 */ - public function testGetTableNames_isFromPrefixedTableName(): void + public function testGetTableNamesIsFromPrefixedTableName(): void { $query = $this->createQuery(); $query->from = '{{%order_item}}'; @@ -117,7 +117,7 @@ public function testGetTableNames_isFromPrefixedTableName(): void /** * @see https://github.com/yiisoft/yii2/issues/14211 */ - public function testGetTableNames_isFromTableNameWithDatabase(): void + public function testGetTableNamesIsFromTableNameWithDatabase(): void { $query = $this->createQuery(); $query->from = 'tickets.workflows'; @@ -129,7 +129,7 @@ public function testGetTableNames_isFromTableNameWithDatabase(): void ], $tables); } - public function testGetTableNames_isFromAliasedExpression(): void + public function testGetTableNamesIsFromAliasedExpression(): void { $query = $this->createQuery(); $expression = new Expression('(SELECT id FROM user)'); @@ -142,7 +142,7 @@ public function testGetTableNames_isFromAliasedExpression(): void $this->assertEquals(['{{x}}' => $expression], $tables); } - public function testGetTableNames_isFromAliasedArrayWithExpression(): void + public function testGetTableNamesIsFromAliasedArrayWithExpression(): void { $query = $this->createQuery(); $query->from = ['x' => new Expression('(SELECT id FROM user)')]; @@ -154,7 +154,7 @@ public function testGetTableNames_isFromAliasedArrayWithExpression(): void ], $tables); } - public function testGetTableNames_isFromAliasedSubquery(): void + public function testGetTableNamesIsFromAliasedSubquery(): void { $query = $this->createQuery(); $subQuery = $this->createQuery(); diff --git a/tests/framework/di/InstanceTest.php b/tests/framework/di/InstanceTest.php index abc74fc298c..1c311964d81 100644 --- a/tests/framework/di/InstanceTest.php +++ b/tests/framework/di/InstanceTest.php @@ -57,7 +57,7 @@ public function testEnsure(): void /** * ensure an InvalidConfigException is thrown when a component does not exist. */ - public function testEnsure_NonExistingComponentException(): void + public function testEnsureNonExistingComponentException(): void { $container = new Container(); $this->expectException('yii\base\InvalidConfigException'); @@ -68,7 +68,7 @@ public function testEnsure_NonExistingComponentException(): void /** * ensure an InvalidConfigException is thrown when a class does not exist. */ - public function testEnsure_NonExistingClassException(): void + public function testEnsureNonExistingClassException(): void { $container = new Container(); $this->expectException('yii\base\InvalidConfigException'); @@ -76,7 +76,7 @@ public function testEnsure_NonExistingClassException(): void Instance::ensure('yii\cache\DoesNotExist', 'yii\cache\Cache', $container); } - public function testEnsure_WithoutType(): void + public function testEnsureWithoutType(): void { $container = new Container(); $container->set('db', [ @@ -89,7 +89,7 @@ public function testEnsure_WithoutType(): void $this->assertInstanceOf('\\yii\\db\\Connection', Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], null, $container)); } - public function testEnsure_MinimalSettings(): void + public function testEnsureMinimalSettings(): void { Yii::$container->set('db', [ 'class' => 'yii\db\Connection', diff --git a/tests/framework/grid/DataColumnTest.php b/tests/framework/grid/DataColumnTest.php index 1a0ee8c7b3d..be25e574ab2 100644 --- a/tests/framework/grid/DataColumnTest.php +++ b/tests/framework/grid/DataColumnTest.php @@ -29,7 +29,7 @@ class DataColumnTest extends TestCase /** * @see DataColumn::getHeaderCellLabel() */ - public function testColumnLabels_OnEmpty_ArrayProvider(): void + public function testColumnLabelsOnEmptyArrayProvider(): void { $this->mockApplication(); $grid = new GridView([ @@ -50,7 +50,7 @@ public function testColumnLabels_OnEmpty_ArrayProvider(): void /** * @see DataColumn::getHeaderCellLabel() */ - public function testColumnLabels_OnEmpty_ArrayProvider_WithFilterModel(): void + public function testColumnLabelsOnEmptyArrayProviderWithFilterModel(): void { $this->mockApplication(); $grid = new GridView([ @@ -72,7 +72,7 @@ public function testColumnLabels_OnEmpty_ArrayProvider_WithFilterModel(): void * @see DataColumn::$filter * @see DataColumn::renderFilterCellContent() */ - public function testFilterInput_String(): void + public function testFilterInputString(): void { $this->mockApplication(); $filterInput = ''; @@ -133,7 +133,7 @@ public function testFilterHasMaxLengthWhenIsAnActiveTextInput(): void * @see DataColumn::$filter * @see DataColumn::renderFilterCellContent() */ - public function testFilterInput_Array(): void + public function testFilterInputArray(): void { $this->mockApplication([ 'components' => [ @@ -184,7 +184,7 @@ public function testFilterInput_Array(): void * @see DataColumn::$filter * @see DataColumn::renderFilterCellContent() */ - public function testFilterInput_FormatBoolean(): void + public function testFilterInputFormatBoolean(): void { $this->mockApplication([ 'components' => [ diff --git a/tests/framework/helpers/BaseConsoleTest.php b/tests/framework/helpers/BaseConsoleTest.php index 04d1a8ea157..6ad4fb720db 100644 --- a/tests/framework/helpers/BaseConsoleTest.php +++ b/tests/framework/helpers/BaseConsoleTest.php @@ -37,7 +37,7 @@ public function renderColoredString(): void /** * @test */ - public function ansiColorizedSubstr_withoutColors(): void + public function ansiColorizedSubstrWithoutColors(): void { $str = 'FooBar'; @@ -56,13 +56,13 @@ public function ansiColorizedSubstr_withoutColors(): void /** * @test - * @dataProvider ansiColorizedSubstr_withColors_data + * @dataProvider ansiColorizedSubstrWithColorsData * @param $str * @param $start * @param $length * @param $expected */ - public function ansiColorizedSubstr_withColors($str, $start, $length, $expected): void + public function ansiColorizedSubstrWithColors($str, $start, $length, $expected): void { $ansiStr = BaseConsole::renderColoredString($str); @@ -71,7 +71,7 @@ public function ansiColorizedSubstr_withColors($str, $start, $length, $expected) $this->assertEquals($ansiExpected, $ansiActual); } - public static function ansiColorizedSubstr_withColors_data(): array + public static function ansiColorizedSubstrWithColorsData(): array { return [ ['%rFoo%gBar%n', 0, 3, '%rFoo%n'], diff --git a/tests/framework/helpers/HtmlTest.php b/tests/framework/helpers/HtmlTest.php index 3d7fe06bef4..ba34ac7217a 100644 --- a/tests/framework/helpers/HtmlTest.php +++ b/tests/framework/helpers/HtmlTest.php @@ -1612,10 +1612,10 @@ public function testActivePasswordInput($value, array $options, $expectedHtml): } /** - * Data provider for [[testActiveInput_TypeText]]. + * Data provider for [[testActiveInputTypeText]]. * @return array test data */ - public static function dataProviderActiveInput_TypeText(): array + public static function dataProviderActiveInputTypeText(): array { return [ [ @@ -1641,13 +1641,13 @@ public static function dataProviderActiveInput_TypeText(): array } /** - * @dataProvider dataProviderActiveInput_TypeText + * @dataProvider dataProviderActiveInputTypeText * * @param string $value * @param array $options * @param string $expectedHtml */ - public function testActiveInput_TypeText($value, array $options, $expectedHtml): void + public function testActiveInputTypeText($value, array $options, $expectedHtml): void { $model = new HtmlTestModel(); $model->name = $value; @@ -2169,7 +2169,7 @@ public function testActiveCheckboxList(): void $this->assertEqualsWithoutLE($expected, $actual); } - public function testActiveCheckboxList_options(): void + public function testActiveCheckboxListOptions(): void { $model = new HtmlTestModel(); @@ -2180,7 +2180,7 @@ public function testActiveCheckboxList_options(): void $this->assertEqualsWithoutLE($expected, $actual); } - public function testActiveTextInput_placeholderFillFromModel(): void + public function testActiveTextInputPlaceholderFillFromModel(): void { $model = new HtmlTestModel(); @@ -2189,7 +2189,7 @@ public function testActiveTextInput_placeholderFillFromModel(): void $this->assertStringContainsString('placeholder="Name"', $html); } - public function testActiveTextInput_customPlaceholder(): void + public function testActiveTextInputCustomPlaceholder(): void { $model = new HtmlTestModel(); @@ -2198,7 +2198,7 @@ public function testActiveTextInput_customPlaceholder(): void $this->assertStringContainsString('placeholder="Custom placeholder"', $html); } - public function testActiveTextInput_placeholderFillFromModelTabular(): void + public function testActiveTextInputPlaceholderFillFromModelTabular(): void { $model = new HtmlTestModel(); diff --git a/tests/framework/helpers/StringHelperTest.php b/tests/framework/helpers/StringHelperTest.php index 2aad94a7b83..8ffdf43a071 100644 --- a/tests/framework/helpers/StringHelperTest.php +++ b/tests/framework/helpers/StringHelperTest.php @@ -404,7 +404,7 @@ public function testMatchWildcard($pattern, $string, $expectedResult, $options = $this->assertSame($expectedResult, StringHelper::matchWildcard($pattern, $string, $options)); } - public static function dataProviderMb_ucfirst(): array + public static function dataProviderMbUcfirst(): array { return [ ['foo', 'Foo'], @@ -419,14 +419,14 @@ public static function dataProviderMb_ucfirst(): array /** * @param string $string * @param string $expectedResult - * @dataProvider dataProviderMb_ucfirst + * @dataProvider dataProviderMbUcfirst */ - public function testMb_ucfirst($string, $expectedResult): void + public function testMbUcfirst($string, $expectedResult): void { $this->assertSame($expectedResult, StringHelper::mb_ucfirst($string)); } - public static function dataProviderMb_ucwords(): array + public static function dataProviderMbUcwords(): array { return [ ['foo', 'Foo'], @@ -448,9 +448,9 @@ public static function dataProviderMb_ucwords(): array /** * @param string $string * @param string $expectedResult - * @dataProvider dataProviderMb_ucwords + * @dataProvider dataProviderMbUcwords */ - public function testMb_ucwords($string, $expectedResult): void + public function testMbUcwords($string, $expectedResult): void { $this->assertSame($expectedResult, StringHelper::mb_ucwords($string)); } diff --git a/tests/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php index c9753b07011..d5c647d7491 100644 --- a/tests/framework/validators/FileValidatorTest.php +++ b/tests/framework/validators/FileValidatorTest.php @@ -228,7 +228,7 @@ public function testValidateAttributeMultiple(): void $this->assertFalse($m->validate()); } - public function testValidateAttribute_minFilesGreaterThanOneMaxFilesUnlimited_notError(): void + public function testValidateAttributeMinFilesGreaterThanOneMaxFilesUnlimitedNotError(): void { $validator = new FileValidator(['minFiles' => 2, 'maxFiles' => 0]); $model = FakedValidationModel::createWithAttributes( @@ -255,7 +255,7 @@ public function testValidateAttribute_minFilesGreaterThanOneMaxFilesUnlimited_no $this->assertFalse($model->hasErrors('attr_images')); } - public function testValidateAttribute_minFilesTwoMaxFilesFour_notError(): void + public function testValidateAttributeMinFilesTwoMaxFilesFourNotError(): void { $validator = new FileValidator(['minFiles' => 2, 'maxFiles' => 4]); $model = FakedValidationModel::createWithAttributes( @@ -282,7 +282,7 @@ public function testValidateAttribute_minFilesTwoMaxFilesFour_notError(): void $this->assertFalse($model->hasErrors('attr_images')); } - public function testValidateAttribute_minFilesTwoMaxFilesUnlimited_hasError(): void + public function testValidateAttributeMinFilesTwoMaxFilesUnlimitedHasError(): void { $validator = new FileValidator(['minFiles' => 2, 'maxFiles' => 0]); $model = FakedValidationModel::createWithAttributes( diff --git a/tests/framework/web/AssetBundleTest.php b/tests/framework/web/AssetBundleTest.php index f1e4bad4813..3f9753e4fd3 100644 --- a/tests/framework/web/AssetBundleTest.php +++ b/tests/framework/web/AssetBundleTest.php @@ -80,11 +80,11 @@ public function testSourcesPublish(): void $bundle->publish($am); $this->assertTrue(is_dir($bundle->basePath)); - $this->sourcesPublish_VerifyFiles('css', $bundle); - $this->sourcesPublish_VerifyFiles('js', $bundle); + $this->sourcesPublishVerifyFiles('css', $bundle); + $this->sourcesPublishVerifyFiles('js', $bundle); } - private function sourcesPublish_VerifyFiles($type, $bundle): void + private function sourcesPublishVerifyFiles($type, $bundle): void { foreach ($bundle->$type as $filename) { $publishedFile = $bundle->basePath . DIRECTORY_SEPARATOR . $filename; @@ -101,7 +101,7 @@ public function testSourcesPublishedBySymlink(): void $this->verifySourcesPublishedBySymlink($view); } - public function testSourcesPublishedBySymlink_Issue9333(): void + public function testSourcesPublishedBySymlinkIssue9333(): void { $view = $this->getView([ 'linkAssets' => true, @@ -113,7 +113,7 @@ public function testSourcesPublishedBySymlink_Issue9333(): void $this->assertTrue(is_dir(dirname($bundle->basePath))); } - public function testSourcesPublish_AssetManagerBeforeCopy(): void + public function testSourcesPublishAssetManagerBeforeCopy(): void { $view = $this->getView([ 'beforeCopy' => function ($from, $to) { @@ -132,7 +132,7 @@ public function testSourcesPublish_AssetManagerBeforeCopy(): void } } - public function testSourcesPublish_AssetBeforeCopy(): void + public function testSourcesPublishAssetBeforeCopy(): void { $view = $this->getView(); $am = $view->assetManager; @@ -152,7 +152,7 @@ public function testSourcesPublish_AssetBeforeCopy(): void } } - public function testSourcesPublish_publishOptions_Only(): void + public function testSourcesPublishPublishOptionsOnly(): void { $view = $this->getView(); $am = $view->assetManager; From 4b61d62d0aa36ef3eb8c1ed65168d6de52b96fb8 Mon Sep 17 00:00:00 2001 From: Maksim Spirkov <63721828+mspirkov@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:57:05 +0300 Subject: [PATCH 7/7] Fix #20637: Fix PHPDoc annotations in `ActiveRelationTrait`. Add PHPStan/Psalm annotations for `$modelClass` in `ActiveQuery`, `ActiveQueryTrait` and `ActiveRelationTrait` --- framework/CHANGELOG.md | 1 + framework/db/ActiveQuery.php | 3 +++ framework/db/ActiveQueryTrait.php | 3 +++ framework/db/ActiveRelationTrait.php | 5 ++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 765685057e8..3c04f87cacb 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -69,6 +69,7 @@ Yii Framework 2 Change Log - Bug #20630: Fix `@var` annotations for `yii\web\CompositeUrlRule::$rules` and `yii\web\GroupUrlRule::$rules` (mspirkov) - Bug #20634: Fix PHPDoc annotations in `yii\db` namespace. Add PHPStan/Psalm annotations for `yii\db\SqlTokenizer::startsWithAnyLongest()` (mspirkov) - Bug #20636: Fix `@param` annotations for `$message` in logging methods (mspirkov) +- Bug #20637: Fix PHPDoc annotations in `ActiveRelationTrait`. Add PHPStan/Psalm annotations for `$modelClass` in `ActiveQuery`, `ActiveQueryTrait` and `ActiveRelationTrait` (mspirkov) - Bug #20639: Add missing generics in `yii\web` namespace (mspirkov) diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index fa56a600f4a..1fcf0fc23cc 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -120,6 +120,9 @@ class ActiveQuery extends Query implements ActiveQueryInterface * Constructor. * @param string $modelClass the model class associated with this query * @param array $config configurations to be applied to the newly created query object + * + * @phpstan-param class-string $modelClass + * @psalm-param class-string $modelClass */ public function __construct($modelClass, $config = []) { diff --git a/framework/db/ActiveQueryTrait.php b/framework/db/ActiveQueryTrait.php index d793c8efc52..f80693795a1 100644 --- a/framework/db/ActiveQueryTrait.php +++ b/framework/db/ActiveQueryTrait.php @@ -18,6 +18,9 @@ trait ActiveQueryTrait { /** * @var string the name of the ActiveRecord class. + * + * @phpstan-var class-string + * @psalm-var class-string */ public $modelClass; /** diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php index 2347098ff71..266b2bac2be 100644 --- a/framework/db/ActiveRelationTrait.php +++ b/framework/db/ActiveRelationTrait.php @@ -20,7 +20,10 @@ * * @method ActiveRecordInterface|array|null one($db = null) See [[ActiveQueryInterface::one()]] for more info. * @method ActiveRecordInterface[] all($db = null) See [[ActiveQueryInterface::all()]] for more info. - * @property ActiveRecord $modelClass + * @property string $modelClass + * + * @phpstan-property class-string $modelClass + * @psalm-property class-string $modelClass */ trait ActiveRelationTrait {