Skip to content

Commit 4952361

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into 4.8
2 parents ab01d5e + 451b656 commit 4952361

File tree

17 files changed

+350
-20
lines changed

17 files changed

+350
-20
lines changed

.github/labeler.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# https://github.com/actions/labeler?tab=readme-ov-file#usage
2+
3+
# Add the `4.8` label to PRs that target the `4.8` branch.
4+
'4.8': # @todo change value whenever the next minor version is changed
5+
- base-branch: '4.8'
6+
7+
# Add the `github_actions` label to PRs that change any file in the `.github/workflows/` directory.
8+
'github_actions':
9+
- changed-files:
10+
- any-glob-to-any-file:
11+
- '.github/workflows/*'
12+
13+
# Add the `documentation` label to PRs that change any file in the `user_guide_src/source/` directory.
14+
'documentation':
15+
- changed-files:
16+
- any-glob-to-all-files:
17+
- 'user_guide_src/source/*'
18+
19+
# Add the `testing` label to PRs that change files in the `tests/` directory ONLY.
20+
'testing':
21+
- changed-files:
22+
- any-glob-to-all-files:
23+
- 'tests/*'

.github/scripts/secure-git-push

100644100755
File mode changed.

.github/workflows/deploy-apidocs.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ on:
88
branches:
99
- 'develop'
1010
paths:
11-
- 'system/**'
11+
- '.github/scripts/secure-git-push'
1212
- '.github/workflows/deploy-apidocs.yml'
13+
- 'system/**'
1314

1415
permissions:
1516
contents: read
@@ -72,7 +73,11 @@ jobs:
7273
PUSH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
7374
run: |
7475
git add .
76+
7577
if ! git diff-index --quiet HEAD; then
7678
git commit -m "Updated API for commit ${GITHUB_SHA}"
77-
bash ${GITHUB_WORKSPACE}/.github/scripts/secure-git-push https://github.com/codeigniter4/api.git HEAD:master
79+
bash "${GITHUB_WORKSPACE}/source/.github/scripts/secure-git-push" https://github.com/codeigniter4/api.git HEAD:master
80+
echo "API documentation deployed successfully."
81+
else
82+
echo "No changes to deploy."
7883
fi

.github/workflows/label-pr.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Add Labels to PRs
2+
3+
# NOTE: When updating this workflow, you should first change the event to `pull_request` to test the changes
4+
# in a PR, and then change it back to `pull_request_target` before merging.
5+
# @see https://github.com/actions/labeler?tab=readme-ov-file#updating-major-version-of-the-labeler
6+
on:
7+
- pull_request_target
8+
9+
jobs:
10+
add-labels:
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
runs-on: ubuntu-24.04
15+
16+
steps:
17+
- name: Add labels
18+
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
19+
with:
20+
sync-labels: true # Remove labels when matching files are reverted

admin/apibot.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The in-progress CI4 API docs, warts & all, are rebuilt and
66
then copied to a nested
77
repository clone (`build/api`), with the result
88
optionally pushed to the `master` branch of the `api` repo.
9-
That would then be publically visible as the in-progress
9+
That would then be publicly visible as the in-progress
1010
version of the [API](https://codeigniter4.github.io/api/).
1111

1212
## Requirements

admin/docbot.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Builds & deploys user guide.
55
The in-progress CI4 user guide, warts & all, is rebuilt in a nested
66
repository clone (`user_guide_src/build/html`), with the result
77
optionally pushed to the `gh-pages` branch of the repo.
8-
That would then be publically visible as the in-progress
8+
That would then be publicly visible as the in-progress
99
version of the [User Guide](https://codeigniter4.github.io/CodeIgniter4/).
1010

1111
## Requirements

rector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154

155155
CompactToVariablesRector::class,
156156

157-
// possibly isset() on purpose, on updated Config classes property accross versions
157+
// possibly isset() on purpose, on updated Config classes property across versions
158158
IssetOnPropertyObjectToPropertyExistsRector::class,
159159

160160
// some tests extended by other tests

system/Database/BaseConnection.php

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
use CodeIgniter\Events\Events;
1919
use CodeIgniter\I18n\Time;
2020
use Exception;
21+
use ReflectionClass;
22+
use ReflectionNamedType;
23+
use ReflectionType;
24+
use ReflectionUnionType;
2125
use stdClass;
2226
use Stringable;
2327
use Throwable;
@@ -60,6 +64,13 @@
6064
*/
6165
abstract class BaseConnection implements ConnectionInterface
6266
{
67+
/**
68+
* Cached builtin type names per class/property.
69+
*
70+
* @var array<class-string, array<string, list<string>>>
71+
*/
72+
private static array $propertyBuiltinTypesCache = [];
73+
6374
/**
6475
* Data Source Name / Connect string
6576
*
@@ -384,9 +395,14 @@ public function __construct(array $params)
384395
unset($params['dateFormat']);
385396
}
386397

398+
$typedPropertyTypes = $this->getBuiltinPropertyTypesMap(array_keys($params));
399+
387400
foreach ($params as $key => $value) {
388401
if (property_exists($this, $key)) {
389-
$this->{$key} = $value;
402+
$this->{$key} = $this->castScalarValueForTypedProperty(
403+
$value,
404+
$typedPropertyTypes[$key] ?? [],
405+
);
390406
}
391407
}
392408

@@ -404,6 +420,126 @@ public function __construct(array $params)
404420
}
405421
}
406422

423+
/**
424+
* Some config values (especially env overrides without clear source type)
425+
* can still reach us as strings. Coerce them for typed properties to keep
426+
* strict typing compatible.
427+
*
428+
* @param list<string> $types
429+
*/
430+
private function castScalarValueForTypedProperty(mixed $value, array $types): mixed
431+
{
432+
if (! is_string($value)) {
433+
return $value;
434+
}
435+
436+
if ($types === [] || in_array('string', $types, true) || in_array('mixed', $types, true)) {
437+
return $value;
438+
}
439+
440+
$trimmedValue = trim($value);
441+
442+
if (in_array('null', $types, true) && strtolower($trimmedValue) === 'null') {
443+
return null;
444+
}
445+
446+
if (in_array('int', $types, true) && preg_match('/^[+-]?\d+$/', $trimmedValue) === 1) {
447+
return (int) $trimmedValue;
448+
}
449+
450+
if (in_array('float', $types, true) && is_numeric($trimmedValue)) {
451+
return (float) $trimmedValue;
452+
}
453+
454+
if (in_array('bool', $types, true) || in_array('false', $types, true) || in_array('true', $types, true)) {
455+
$boolValue = filter_var($trimmedValue, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
456+
457+
if ($boolValue !== null) {
458+
if (in_array('bool', $types, true)) {
459+
return $boolValue;
460+
}
461+
462+
if ($boolValue === false && in_array('false', $types, true)) {
463+
return false;
464+
}
465+
466+
if ($boolValue === true && in_array('true', $types, true)) {
467+
return true;
468+
}
469+
}
470+
}
471+
472+
return $value;
473+
}
474+
475+
/**
476+
* @param list<string> $properties
477+
*
478+
* @return array<string, list<string>>
479+
*/
480+
private function getBuiltinPropertyTypesMap(array $properties): array
481+
{
482+
$className = static::class;
483+
$requested = array_fill_keys($properties, true);
484+
485+
if (! isset(self::$propertyBuiltinTypesCache[$className])) {
486+
self::$propertyBuiltinTypesCache[$className] = [];
487+
}
488+
489+
// Fill only the properties requested by this call that are not cached yet.
490+
$missing = array_diff_key($requested, self::$propertyBuiltinTypesCache[$className]);
491+
492+
if ($missing !== []) {
493+
$reflection = new ReflectionClass($className);
494+
495+
foreach ($reflection->getProperties() as $property) {
496+
$propertyName = $property->getName();
497+
498+
if (! isset($missing[$propertyName])) {
499+
continue;
500+
}
501+
502+
$type = $property->getType();
503+
504+
if (! $type instanceof ReflectionType) {
505+
self::$propertyBuiltinTypesCache[$className][$propertyName] = [];
506+
507+
continue;
508+
}
509+
510+
$namedTypes = $type instanceof ReflectionUnionType ? $type->getTypes() : [$type];
511+
$builtinTypes = [];
512+
513+
foreach ($namedTypes as $namedType) {
514+
if (! $namedType instanceof ReflectionNamedType || ! $namedType->isBuiltin()) {
515+
continue;
516+
}
517+
518+
$builtinTypes[] = $namedType->getName();
519+
}
520+
521+
if ($type->allowsNull() && ! in_array('null', $builtinTypes, true)) {
522+
$builtinTypes[] = 'null';
523+
}
524+
525+
self::$propertyBuiltinTypesCache[$className][$propertyName] = $builtinTypes;
526+
}
527+
528+
// Untyped or unresolved properties are cached as empty to avoid re-reflecting them.
529+
foreach (array_keys($missing) as $propertyName) {
530+
self::$propertyBuiltinTypesCache[$className][$propertyName] ??= [];
531+
}
532+
}
533+
534+
$typedProperties = [];
535+
536+
foreach ($properties as $property) {
537+
$typedProperties[$property] = self::$propertyBuiltinTypesCache[$className][$property] ?? [];
538+
}
539+
540+
return $typedProperties;
541+
}
542+
407543
/**
408544
* Initializes the database connection/settings.
409545
*
@@ -445,10 +581,15 @@ public function initialize()
445581
if (! empty($this->failover) && is_array($this->failover)) {
446582
// Go over all the failovers
447583
foreach ($this->failover as $index => $failover) {
584+
$typedPropertyTypes = $this->getBuiltinPropertyTypesMap(array_keys($failover));
585+
448586
// Replace the current settings with those of the failover
449587
foreach ($failover as $key => $val) {
450588
if (property_exists($this, $key)) {
451-
$this->{$key} = $val;
589+
$this->{$key} = $this->castScalarValueForTypedProperty(
590+
$val,
591+
$typedPropertyTypes[$key] ?? [],
592+
);
452593
}
453594
}
454595

system/Database/SQLSRV/Builder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ private function getFullName(string $table): string
321321
}
322322

323323
/**
324-
* Add permision statements for index value inserts
324+
* Add permission statements for index value inserts
325325
*/
326326
private function addIdentity(string $fullTable, string $insert): string
327327
{

system/Database/SQLite3/Connection.php

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

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

0 commit comments

Comments
 (0)