Skip to content

Commit e36fbd9

Browse files
committed
refactor
1 parent 61dafe9 commit e36fbd9

File tree

4 files changed

+96
-57
lines changed

4 files changed

+96
-57
lines changed

system/Database/BaseConnection.php

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
use Closure;
1717
use CodeIgniter\Database\Exceptions\DatabaseException;
1818
use CodeIgniter\Events\Events;
19+
use ReflectionClass;
1920
use ReflectionNamedType;
20-
use ReflectionProperty;
2121
use ReflectionType;
2222
use ReflectionUnionType;
2323
use stdClass;
@@ -383,9 +383,14 @@ public function __construct(array $params)
383383
unset($params['dateFormat']);
384384
}
385385

386+
$typedPropertyTypes = $this->getBuiltinPropertyTypesMap(array_keys($params));
387+
386388
foreach ($params as $key => $value) {
387389
if (property_exists($this, $key)) {
388-
$this->{$key} = $this->castScalarValueForTypedProperty($key, $value);
390+
$this->{$key} = $this->castScalarValueForTypedProperty(
391+
$value,
392+
$typedPropertyTypes[$key] ?? [],
393+
);
389394
}
390395
}
391396

@@ -407,15 +412,15 @@ public function __construct(array $params)
407412
* Some config values (especially env overrides without clear source type)
408413
* can still reach us as strings. Coerce them for typed properties to keep
409414
* strict typing compatible.
415+
*
416+
* @param list<string> $types
410417
*/
411-
private function castScalarValueForTypedProperty(string $property, mixed $value): mixed
418+
private function castScalarValueForTypedProperty(mixed $value, array $types): mixed
412419
{
413420
if (! is_string($value)) {
414421
return $value;
415422
}
416423

417-
$types = $this->getBuiltinPropertyTypes($property);
418-
419424
if ($types === [] || in_array('string', $types, true) || in_array('mixed', $types, true)) {
420425
return $value;
421426
}
@@ -456,39 +461,54 @@ private function castScalarValueForTypedProperty(string $property, mixed $value)
456461
}
457462

458463
/**
459-
* @return list<string>
464+
* @param list<string> $properties
465+
*
466+
* @return array<string, list<string>>
460467
*/
461-
private function getBuiltinPropertyTypes(string $property): array
468+
private function getBuiltinPropertyTypesMap(array $properties): array
462469
{
463470
$className = static::class;
464471

465-
if (isset(self::$propertyBuiltinTypesCache[$className][$property])) {
466-
return self::$propertyBuiltinTypesCache[$className][$property];
467-
}
472+
if (! isset(self::$propertyBuiltinTypesCache[$className])) {
473+
self::$propertyBuiltinTypesCache[$className] = [];
468474

469-
$type = (new ReflectionProperty($className, $property))->getType();
475+
$reflection = new ReflectionClass($className);
470476

471-
if (! $type instanceof ReflectionType) {
472-
return self::$propertyBuiltinTypesCache[$className][$property] = [];
473-
}
477+
foreach ($reflection->getProperties() as $property) {
478+
$type = $property->getType();
474479

475-
$types = $type instanceof ReflectionUnionType ? $type->getTypes() : [$type];
480+
if (! $type instanceof ReflectionType) {
481+
self::$propertyBuiltinTypesCache[$className][$property->getName()] = [];
476482

477-
$builtinTypes = [];
483+
continue;
484+
}
478485

479-
foreach ($types as $namedType) {
480-
if (! $namedType instanceof ReflectionNamedType || ! $namedType->isBuiltin()) {
481-
continue;
482-
}
486+
$namedTypes = $type instanceof ReflectionUnionType ? $type->getTypes() : [$type];
487+
$builtinTypes = [];
483488

484-
$builtinTypes[] = $namedType->getName();
489+
foreach ($namedTypes as $namedType) {
490+
if (! $namedType instanceof ReflectionNamedType || ! $namedType->isBuiltin()) {
491+
continue;
492+
}
493+
494+
$builtinTypes[] = $namedType->getName();
495+
}
496+
497+
if ($type->allowsNull() && ! in_array('null', $builtinTypes, true)) {
498+
$builtinTypes[] = 'null';
499+
}
500+
501+
self::$propertyBuiltinTypesCache[$className][$property->getName()] = $builtinTypes;
502+
}
485503
}
486504

487-
if ($type->allowsNull() && ! in_array('null', $builtinTypes, true)) {
488-
$builtinTypes[] = 'null';
505+
$typedProperties = [];
506+
507+
foreach ($properties as $property) {
508+
$typedProperties[$property] = self::$propertyBuiltinTypesCache[$className][$property] ?? [];
489509
}
490510

491-
return self::$propertyBuiltinTypesCache[$className][$property] = $builtinTypes;
511+
return $typedProperties;
492512
}
493513

494514
/**
@@ -532,10 +552,15 @@ public function initialize()
532552
if (! empty($this->failover) && is_array($this->failover)) {
533553
// Go over all the failovers
534554
foreach ($this->failover as $index => $failover) {
555+
$typedPropertyTypes = $this->getBuiltinPropertyTypesMap(array_keys($failover));
556+
535557
// Replace the current settings with those of the failover
536558
foreach ($failover as $key => $val) {
537559
if (property_exists($this, $key)) {
538-
$this->{$key} = $this->castScalarValueForTypedProperty($key, $val);
560+
$this->{$key} = $this->castScalarValueForTypedProperty(
561+
$val,
562+
$typedPropertyTypes[$key] ?? [],
563+
);
539564
}
540565
}
541566

system/Database/SQLite3/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Connection extends BaseConnection
5555
*
5656
* @see https://www.php.net/manual/en/sqlite3.busytimeout
5757
*/
58-
protected $busyTimeout;
58+
protected ?int $busyTimeout = null;
5959

6060
/**
6161
* The setting of the "synchronous" flag

tests/system/Database/BaseConnectionTest.php

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,22 @@ public function testSavesConfigOptions(): void
9898

9999
public function testCastsStringConfigValuesToTypedProperties(): void
100100
{
101-
$db = new class ([
102-
...$this->options,
103-
'synchronous' => '1',
104-
'typedBool' => '0',
105-
'nullInt' => 'null',
106-
]) extends MockConnection {
101+
$db = new class ([...$this->options, 'synchronous' => '1', 'busyTimeout' => '4000', 'typedBool' => '0', 'nullInt' => 'null']) extends MockConnection {
107102
protected ?int $synchronous = null;
108-
protected bool $typedBool = true;
109-
protected ?int $nullInt = 1;
103+
protected ?int $busyTimeout = null;
104+
protected bool $typedBool = true;
105+
protected ?int $nullInt = 1;
110106

111107
public function getSynchronous(): ?int
112108
{
113109
return $this->synchronous;
114110
}
115111

112+
public function getBusyTimeout(): ?int
113+
{
114+
return $this->busyTimeout;
115+
}
116+
116117
public function isTypedBool(): bool
117118
{
118119
return $this->typedBool;
@@ -125,28 +126,38 @@ public function getNullInt(): ?int
125126
};
126127

127128
$this->assertSame(1, $db->getSynchronous());
129+
$this->assertSame(4000, $db->getBusyTimeout());
128130
$this->assertFalse($db->isTypedBool());
129131
$this->assertNull($db->getNullInt());
130132
}
131133

132134
public function testCastsExtendedBoolStringsToBool(): void
133135
{
134-
$db = new class ([
135-
...$this->options,
136-
'enabledYes' => 'yes',
137-
'enabledOn' => 'on',
138-
'disabledNo' => 'no',
139-
'disabledOff' => 'off',
140-
]) extends MockConnection {
136+
$db = new class ([...$this->options, 'enabledYes' => 'yes', 'enabledOn' => 'on', 'disabledNo' => 'no', 'disabledOff' => 'off']) extends MockConnection {
141137
protected bool $enabledYes = false;
142138
protected bool $enabledOn = false;
143139
protected bool $disabledNo = true;
144140
protected bool $disabledOff = true;
145141

146-
public function isEnabledYes(): bool { return $this->enabledYes; }
147-
public function isEnabledOn(): bool { return $this->enabledOn; }
148-
public function isDisabledNo(): bool { return $this->disabledNo; }
149-
public function isDisabledOff(): bool { return $this->disabledOff; }
142+
public function isEnabledYes(): bool
143+
{
144+
return $this->enabledYes;
145+
}
146+
147+
public function isEnabledOn(): bool
148+
{
149+
return $this->enabledOn;
150+
}
151+
152+
public function isDisabledNo(): bool
153+
{
154+
return $this->disabledNo;
155+
}
156+
157+
public function isDisabledOff(): bool
158+
{
159+
return $this->disabledOff;
160+
}
150161
};
151162

152163
$this->assertTrue($db->isEnabledYes());
@@ -157,16 +168,19 @@ public function isDisabledOff(): bool { return $this->disabledOff; }
157168

158169
public function testCastsFalseAndTrueStandaloneUnionTypes(): void
159170
{
160-
$db = new class ([
161-
...$this->options,
162-
'withFalse' => 'false',
163-
'withTrue' => 'true',
164-
]) extends MockConnection {
165-
protected int|false $withFalse = 0;
171+
$db = new class ([...$this->options, 'withFalse' => 'false', 'withTrue' => 'true']) extends MockConnection {
172+
protected false|int $withFalse = 0;
166173
protected int|true $withTrue = 0;
167174

168-
public function getWithFalse(): int|false { return $this->withFalse; }
169-
public function getWithTrue(): int|true { return $this->withTrue; }
175+
public function getWithFalse(): false|int
176+
{
177+
return $this->withFalse;
178+
}
179+
180+
public function getWithTrue(): int|true
181+
{
182+
return $this->withTrue;
183+
}
170184
};
171185

172186
$this->assertFalse($db->getWithFalse());
@@ -177,10 +191,7 @@ public function testInvalidStringValueForTypedPropertyThrowsTypeError(): void
177191
{
178192
$this->expectException(TypeError::class);
179193

180-
new class ([
181-
...$this->options,
182-
'synchronous' => 'not-an-int',
183-
]) extends MockConnection {
194+
new class ([...$this->options, 'synchronous' => 'not-an-int']) extends MockConnection {
184195
protected ?int $synchronous = null;
185196
};
186197
}

user_guide_src/source/changelogs/v4.7.1.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Release Date: Unreleased
1414
BREAKING
1515
********
1616

17+
- **Database:** ``CodeIgniter\Database\SQLite3\Connection::$busyTimeout`` is now typed as ``?int``. Custom subclasses that redeclare this property will need to be updated.
18+
1719
***************
1820
Message Changes
1921
***************
@@ -53,6 +55,7 @@ Bugs Fixed
5355
- **ContentSecurityPolicy:** Fixed a bug where nonces generated by ``getScriptNonce()`` and ``getStyleNonce()`` were not added to the ``script-src-elem`` and ``style-src-elem`` directives, causing nonces to be silently ignored by browsers when those directives were present.
5456
- **Database:** Fixed a bug where ``BaseConnection::callFunction()`` could double-prefix already-prefixed function names.
5557
- **Database:** Fixed a bug where ``BasePreparedQuery::prepare()`` could mis-handle SQL containing colon syntax by over-broad named-placeholder replacement. It now preserves PostgreSQL cast syntax like ``::timestamp``.
58+
- **Database:** Fixed a bug where string values from config arrays (including ``.env`` overrides) were not normalized for typed connection properties, which could cause SQLite3 options like ``synchronous`` and ``busyTimeout`` to be assigned with the wrong type.
5659
- **Model:** Fixed a bug where ``BaseModel::updateBatch()`` threw an exception when ``updateOnlyChanged`` was ``true`` and the index field value did not change.
5760
- **Model:** Fixed a bug where ``Model::chunk()`` ran an unnecessary extra database query at the end of iteration. ``chunk()`` now also throws ``InvalidArgumentException`` when called with a non-positive chunk size.
5861
- **Session:** Fixed a bug in ``MemcachedHandler`` where the constructor incorrectly threw an exception when ``savePath`` was not empty.

0 commit comments

Comments
 (0)