Skip to content

Commit 457a1e7

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into 4.6
2 parents 1bffa3c + 90cff96 commit 457a1e7

File tree

8 files changed

+114
-53
lines changed

8 files changed

+114
-53
lines changed

phpstan-baseline.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,12 +1807,6 @@
18071807
'count' => 1,
18081808
'path' => __DIR__ . '/system/Database/BaseBuilder.php',
18091809
];
1810-
$ignoreErrors[] = [
1811-
// identifier: missingType.iterableValue
1812-
'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:select\\(\\) has parameter \\$select with no value type specified in iterable type array\\.$#',
1813-
'count' => 1,
1814-
'path' => __DIR__ . '/system/Database/BaseBuilder.php',
1815-
];
18161810
$ignoreErrors[] = [
18171811
// identifier: missingType.iterableValue
18181812
'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:set\\(\\) has parameter \\$key with no value type specified in iterable type array\\.$#',
@@ -1981,12 +1975,6 @@
19811975
'count' => 1,
19821976
'path' => __DIR__ . '/system/Database/BaseBuilder.php',
19831977
];
1984-
$ignoreErrors[] = [
1985-
// identifier: missingType.iterableValue
1986-
'message' => '#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$QBNoEscape type has no value type specified in iterable type array\\.$#',
1987-
'count' => 1,
1988-
'path' => __DIR__ . '/system/Database/BaseBuilder.php',
1989-
];
19901978
$ignoreErrors[] = [
19911979
// identifier: missingType.iterableValue
19921980
'message' => '#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$QBOptions type has no value type specified in iterable type array\\.$#',

rector.php

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,9 @@
4242
use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector;
4343
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
4444
use Rector\Php70\Rector\FuncCall\RandomFunctionRector;
45-
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
4645
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
4746
use Rector\Php80\Rector\FunctionLike\MixedTypeRector;
4847
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
49-
use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector;
50-
use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\CoversAnnotationWithValueToAttributeRector;
51-
use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector;
52-
use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DependsAnnotationWithValueToAttributeRector;
5348
use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector;
5449
use Rector\PHPUnit\Set\PHPUnitSetList;
5550
use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector;
@@ -58,6 +53,7 @@
5853
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
5954
use Rector\TypeDeclaration\Rector\Closure\AddClosureVoidReturnTypeWhereNoReturnRector;
6055
use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector;
56+
use Rector\TypeDeclaration\Rector\Function_\AddFunctionVoidReturnTypeWhereNoReturnRector;
6157
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
6258
use Utils\Rector\PassStrictParameterToFunctionParameterRector;
6359
use Utils\Rector\RemoveErrorSuppressInTryCatchStmtsRector;
@@ -175,13 +171,6 @@
175171

176172
// Unnecessary (string) is inserted
177173
NullToStrictStringFuncCallArgRector::class,
178-
179-
// PHPUnit 10 (requires PHP 8.1) features
180-
DataProviderAnnotationToAttributeRector::class,
181-
DependsAnnotationWithValueToAttributeRector::class,
182-
AnnotationWithValueToAttributeRector::class,
183-
AnnotationToAttributeRector::class,
184-
CoversAnnotationWithValueToAttributeRector::class,
185174
])
186175
// auto import fully qualified class names
187176
->withImportNames(removeUnusedImports: true)
@@ -219,6 +208,7 @@
219208
VersionCompareFuncCallToConstantRector::class,
220209
ExplicitBoolCompareRector::class,
221210
AddClosureVoidReturnTypeWhereNoReturnRector::class,
211+
AddFunctionVoidReturnTypeWhereNoReturnRector::class,
222212
])
223213
->withConfiguredRule(StringClassNameToClassConstantRector::class, [
224214
// keep '\\' prefix string on string '\Foo\Bar'

system/Common.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -768,10 +768,8 @@ function lang(string $line, array $args = [], ?string $locale = null)
768768
* - notice
769769
* - info
770770
* - debug
771-
*
772-
* @return void
773771
*/
774-
function log_message(string $level, string $message, array $context = [])
772+
function log_message(string $level, string $message, array $context = []): void
775773
{
776774
// When running tests, we want to always ensure that the
777775
// TestLogger is running, which provides utilities for

system/Database/BaseBuilder.php

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ class BaseBuilder
124124
protected array $QBUnion = [];
125125

126126
/**
127-
* QB NO ESCAPE data
127+
* Whether to protect identifiers in SELECT
128128
*
129-
* @var array
129+
* @var list<bool|null> true=protect, false=not protect
130130
*/
131131
public $QBNoEscape = [];
132132

@@ -390,7 +390,8 @@ public function ignore(bool $ignore = true)
390390
/**
391391
* Generates the SELECT portion of the query
392392
*
393-
* @param array|RawSql|string $select
393+
* @param list<RawSql|string>|RawSql|string $select
394+
* @param bool|null $escape Whether to protect identifiers
394395
*
395396
* @return $this
396397
*/
@@ -402,16 +403,21 @@ public function select($select = '*', ?bool $escape = null)
402403
}
403404

404405
if ($select instanceof RawSql) {
405-
$this->QBSelect[] = $select;
406-
407-
return $this;
406+
$select = [$select];
408407
}
409408

410409
if (is_string($select)) {
411-
$select = $escape === false ? [$select] : explode(',', $select);
410+
$select = ($escape === false) ? [$select] : explode(',', $select);
412411
}
413412

414413
foreach ($select as $val) {
414+
if ($val instanceof RawSql) {
415+
$this->QBSelect[] = $val;
416+
$this->QBNoEscape[] = false;
417+
418+
continue;
419+
}
420+
415421
$val = trim($val);
416422

417423
if ($val !== '') {
@@ -424,8 +430,10 @@ public function select($select = '*', ?bool $escape = null)
424430
* This prevents NULL being escaped
425431
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1169
426432
*/
427-
if (mb_stripos(trim($val), 'NULL') === 0) {
428-
$escape = false;
433+
if (mb_stripos($val, 'NULL') === 0) {
434+
$this->QBNoEscape[] = false;
435+
436+
continue;
429437
}
430438

431439
$this->QBNoEscape[] = $escape;
@@ -3054,15 +3062,17 @@ protected function compileSelect($selectOverride = false): string
30543062

30553063
if (empty($this->QBSelect)) {
30563064
$sql .= '*';
3057-
} elseif ($this->QBSelect[0] instanceof RawSql) {
3058-
$sql .= (string) $this->QBSelect[0];
30593065
} else {
30603066
// Cycle through the "select" portion of the query and prep each column name.
30613067
// The reason we protect identifiers here rather than in the select() function
30623068
// is because until the user calls the from() function we don't know if there are aliases
30633069
foreach ($this->QBSelect as $key => $val) {
3064-
$noEscape = $this->QBNoEscape[$key] ?? null;
3065-
$this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $noEscape);
3070+
if ($val instanceof RawSql) {
3071+
$this->QBSelect[$key] = (string) $val;
3072+
} else {
3073+
$protect = $this->QBNoEscape[$key] ?? null;
3074+
$this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $protect);
3075+
}
30663076
}
30673077

30683078
$sql .= implode(', ', $this->QBSelect);

system/Helpers/cookie_helper.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
* @param bool|null $httpOnly True makes the cookie accessible via http(s) only (no javascript)
3636
* @param string|null $sameSite The cookie SameSite value
3737
*
38-
* @return void
39-
*
4038
* @see \CodeIgniter\HTTP\Response::setCookie()
4139
*/
4240
function set_cookie(
@@ -49,7 +47,7 @@ function set_cookie(
4947
?bool $secure = null,
5048
?bool $httpOnly = null,
5149
?string $sameSite = null
52-
) {
50+
): void {
5351
$response = service('response');
5452
$response->setCookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly, $sameSite);
5553
}
@@ -92,11 +90,9 @@ function get_cookie($index, bool $xssClean = false, ?string $prefix = '')
9290
* @param string $path the cookie path
9391
* @param string $prefix the cookie prefix
9492
*
95-
* @return void
96-
*
9793
* @see \CodeIgniter\HTTP\Response::deleteCookie()
9894
*/
99-
function delete_cookie($name, string $domain = '', string $path = '/', string $prefix = '')
95+
function delete_cookie($name, string $domain = '', string $path = '/', string $prefix = ''): void
10096
{
10197
service('response')->deleteCookie($name, $domain, $path, $prefix);
10298
}

system/Helpers/kint_helper.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*
2525
* @codeCoverageIgnore Can't be tested ... exits
2626
*/
27-
function dd(...$vars)
27+
function dd(...$vars): void
2828
{
2929
// @codeCoverageIgnoreStart
3030
Kint::$aliases[] = 'dd';
@@ -71,10 +71,8 @@ function d(...$vars)
7171
*/
7272
/**
7373
* trace function
74-
*
75-
* @return void
7674
*/
77-
function trace()
75+
function trace(): void
7876
{
7977
Kint::$aliases[] = 'trace';
8078
Kint::trace();

system/Test/DatabaseTestTrait.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Config\Database;
2020
use Config\Migrations;
2121
use Config\Services;
22+
use PHPUnit\Framework\Attributes\AfterClass;
2223

2324
/**
2425
* DatabaseTestTrait
@@ -228,14 +229,12 @@ public function seed(string $name)
228229
// --------------------------------------------------------------------
229230
// Utility
230231
// --------------------------------------------------------------------
231-
232232
/**
233233
* Reset $doneMigration and $doneSeed
234234
*
235-
* @afterClass
236-
*
237235
* @return void
238236
*/
237+
#[AfterClass]
239238
public static function resetMigrationSeedCount()
240239
{
241240
self::$doneMigration = false;

tests/system/Database/Builder/SelectTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use CodeIgniter\Database\SQLSRV\Builder as SQLSRVBuilder;
2020
use CodeIgniter\Test\CIUnitTestCase;
2121
use CodeIgniter\Test\Mock\MockConnection;
22+
use PHPUnit\Framework\Attributes\DataProvider;
2223
use PHPUnit\Framework\Attributes\Group;
2324

2425
/**
@@ -67,6 +68,65 @@ public function testSelectAcceptsArray(): void
6768
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
6869
}
6970

71+
/**
72+
* @param list<RawSql|string> $select
73+
*/
74+
#[DataProvider('provideSelectAcceptsArrayWithRawSql')]
75+
public function testSelectAcceptsArrayWithRawSql(array $select, string $expected): void
76+
{
77+
$builder = new BaseBuilder('employees', $this->db);
78+
79+
$builder->select($select);
80+
81+
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
82+
}
83+
84+
/**
85+
* @return list<list<RawSql|string>|string>
86+
*/
87+
public static function provideSelectAcceptsArrayWithRawSql(): iterable
88+
{
89+
yield from [
90+
[
91+
[
92+
new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
93+
'employee_id',
94+
],
95+
<<<'SQL'
96+
SELECT IF(salary > 5000, 'High', 'Low') AS salary_level, "employee_id" FROM "employees"
97+
SQL,
98+
],
99+
[
100+
[
101+
'employee_id',
102+
new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
103+
],
104+
<<<'SQL'
105+
SELECT "employee_id", IF(salary > 5000, 'High', 'Low') AS salary_level FROM "employees"
106+
SQL,
107+
],
108+
[
109+
[
110+
new RawSql("CONCAT(first_name, ' ', last_name) AS full_name"),
111+
new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
112+
],
113+
<<<'SQL'
114+
SELECT CONCAT(first_name, ' ', last_name) AS full_name, IF(salary > 5000, 'High', 'Low') AS salary_level FROM "employees"
115+
SQL,
116+
],
117+
[
118+
[
119+
new RawSql("CONCAT(first_name, ' ', last_name) AS full_name"),
120+
'employee_id',
121+
new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
122+
],
123+
<<<'SQL'
124+
SELECT CONCAT(first_name, ' ', last_name) AS full_name, "employee_id", IF(salary > 5000, 'High', 'Low') AS salary_level FROM "employees"
125+
SQL,
126+
],
127+
];
128+
}
129+
70130
public function testSelectAcceptsMultipleColumns(): void
71131
{
72132
$builder = new BaseBuilder('users', $this->db);
@@ -100,6 +160,28 @@ public function testSelectWorksWithComplexSelects(): void
100160
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
101161
}
102162

163+
public function testSelectNullAsInString(): void
164+
{
165+
$builder = new BaseBuilder('users', $this->db);
166+
167+
$builder->select('NULL as field_alias, name');
168+
169+
$expected = 'SELECT NULL as field_alias, "name" FROM "users"';
170+
171+
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
172+
}
173+
174+
public function testSelectNullAsInArray(): void
175+
{
176+
$builder = new BaseBuilder('users', $this->db);
177+
178+
$builder->select(['NULL as field_alias', 'name']);
179+
180+
$expected = 'SELECT NULL as field_alias, "name" FROM "users"';
181+
182+
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
183+
}
184+
103185
/**
104186
* @see https://github.com/codeigniter4/CodeIgniter4/issues/4355
105187
*/

0 commit comments

Comments
 (0)