1616use Closure ;
1717use CodeIgniter \Database \Exceptions \DatabaseException ;
1818use CodeIgniter \Events \Events ;
19+ use ReflectionNamedType ;
20+ use ReflectionProperty ;
21+ use ReflectionType ;
22+ use ReflectionUnionType ;
1923use stdClass ;
2024use Stringable ;
2125use Throwable ;
5963 */
6064abstract class BaseConnection implements ConnectionInterface
6165{
66+ /**
67+ * Cached builtin type names per class/property.
68+ *
69+ * @var array<class-string, array<string, list<string>>>
70+ */
71+ private static array $ propertyBuiltinTypesCache = [];
72+
6273 /**
6374 * Data Source Name / Connect string
6475 *
@@ -374,7 +385,7 @@ public function __construct(array $params)
374385
375386 foreach ($ params as $ key => $ value ) {
376387 if (property_exists ($ this , $ key )) {
377- $ this ->{$ key } = $ value ;
388+ $ this ->{$ key } = $ this -> castScalarValueForTypedProperty ( $ key , $ value) ;
378389 }
379390 }
380391
@@ -392,6 +403,94 @@ public function __construct(array $params)
392403 }
393404 }
394405
406+ /**
407+ * Some config values (especially env overrides without clear source type)
408+ * can still reach us as strings. Coerce them for typed properties to keep
409+ * strict typing compatible.
410+ */
411+ private function castScalarValueForTypedProperty (string $ property , mixed $ value ): mixed
412+ {
413+ if (! is_string ($ value )) {
414+ return $ value ;
415+ }
416+
417+ $ types = $ this ->getBuiltinPropertyTypes ($ property );
418+
419+ if ($ types === [] || in_array ('string ' , $ types , true ) || in_array ('mixed ' , $ types , true )) {
420+ return $ value ;
421+ }
422+
423+ $ trimmedValue = trim ($ value );
424+
425+ if (in_array ('null ' , $ types , true ) && strtolower ($ trimmedValue ) === 'null ' ) {
426+ return null ;
427+ }
428+
429+ if (in_array ('int ' , $ types , true ) && preg_match ('/^[+-]?\d+$/ ' , $ trimmedValue ) === 1 ) {
430+ return (int ) $ trimmedValue ;
431+ }
432+
433+ if (in_array ('float ' , $ types , true ) && is_numeric ($ trimmedValue )) {
434+ return (float ) $ trimmedValue ;
435+ }
436+
437+ if (in_array ('bool ' , $ types , true ) || in_array ('false ' , $ types , true ) || in_array ('true ' , $ types , true )) {
438+ $ boolValue = filter_var ($ trimmedValue , FILTER_VALIDATE_BOOLEAN , FILTER_NULL_ON_FAILURE );
439+
440+ if ($ boolValue !== null ) {
441+ if (in_array ('bool ' , $ types , true )) {
442+ return $ boolValue ;
443+ }
444+
445+ if ($ boolValue === false && in_array ('false ' , $ types , true )) {
446+ return false ;
447+ }
448+
449+ if ($ boolValue === true && in_array ('true ' , $ types , true )) {
450+ return true ;
451+ }
452+ }
453+ }
454+
455+ return $ value ;
456+ }
457+
458+ /**
459+ * @return list<string>
460+ */
461+ private function getBuiltinPropertyTypes (string $ property ): array
462+ {
463+ $ className = static ::class;
464+
465+ if (isset (self ::$ propertyBuiltinTypesCache [$ className ][$ property ])) {
466+ return self ::$ propertyBuiltinTypesCache [$ className ][$ property ];
467+ }
468+
469+ $ type = (new ReflectionProperty ($ className , $ property ))->getType ();
470+
471+ if (! $ type instanceof ReflectionType) {
472+ return self ::$ propertyBuiltinTypesCache [$ className ][$ property ] = [];
473+ }
474+
475+ $ types = $ type instanceof ReflectionUnionType ? $ type ->getTypes () : [$ type ];
476+
477+ $ builtinTypes = [];
478+
479+ foreach ($ types as $ namedType ) {
480+ if (! $ namedType instanceof ReflectionNamedType || ! $ namedType ->isBuiltin ()) {
481+ continue ;
482+ }
483+
484+ $ builtinTypes [] = $ namedType ->getName ();
485+ }
486+
487+ if ($ type ->allowsNull () && ! in_array ('null ' , $ builtinTypes , true )) {
488+ $ builtinTypes [] = 'null ' ;
489+ }
490+
491+ return self ::$ propertyBuiltinTypesCache [$ className ][$ property ] = $ builtinTypes ;
492+ }
493+
395494 /**
396495 * Initializes the database connection/settings.
397496 *
@@ -436,7 +535,7 @@ public function initialize()
436535 // Replace the current settings with those of the failover
437536 foreach ($ failover as $ key => $ val ) {
438537 if (property_exists ($ this , $ key )) {
439- $ this ->{$ key } = $ val ;
538+ $ this ->{$ key } = $ this -> castScalarValueForTypedProperty ( $ key , $ val) ;
440539 }
441540 }
442541
0 commit comments