88 * @copyright Copyright (c) 2025 L. Fischer
99 * @license https://opensource.org/licenses/MIT MIT License
1010 * @link https://github.com/decMuc/PDOdb
11- * @version 1.3.7
11+ * @version 1.3.8
1212 * @inspired-by https://github.com/ThingEngineer/PHP-MySQLi-Database-Class
1313 */
1414
3636final class PDOdb
3737{
3838
39+
3940 // ==[ 01. Connection Management ]==
4041 protected static array $ _instances = []; // All active DB instances
4142 protected array $ connectionsSettings = []; // Connection config per instance
@@ -3631,6 +3632,7 @@ public function rawQuery(string $query, ?array $bindParams = null): mixed
36313632 if (!empty ($ bindParams )) {
36323633 foreach ($ bindParams as $ key => $ value ) {
36333634 $ paramKey = is_int ($ key ) ? $ key + 1 : $ key ;
3635+ $ value = $ this ->_normalizeDecimalStringSimple ($ value );
36343636 $ stmt ->bindValue ($ paramKey , $ value );
36353637 }
36363638 }
@@ -3739,10 +3741,13 @@ public function rawQueryValue(string $query, ?array $bindParams = null): mixed
37393741 protected function _bindParam (mixed $ value ): void
37403742 {
37413743 if (is_array ($ value ) && isset ($ value ['value ' ], $ value ['type ' ])) {
3744+ $ value ['value ' ] = $ this ->_normalizeDecimalStringSimple ($ value ['value ' ]);
37423745 $ this ->_bindParams [] = $ value ;
37433746 return ;
37443747 }
37453748
3749+ $ value = $ this ->_normalizeDecimalStringSimple ($ value );
3750+
37463751 static $ typeMap = [
37473752 'integer ' => \PDO ::PARAM_INT ,
37483753 'boolean ' => \PDO ::PARAM_BOOL ,
@@ -5439,7 +5444,12 @@ protected function _secureSanitizeWhere(array &$stack): void
54395444 $ allowed = (strlen ((string )$ tmp ) === strlen ((string )$ value ));
54405445 }
54415446 elseif (in_array ($ type , ['float ' , 'double ' , 'decimal ' , 'real ' ], true )) {
5442- $ allowed = is_numeric ($ value );
5447+ $ nv = $ this ->_normalizeDecimalStringSimple ($ value );
5448+ if (is_string ($ nv )) {
5449+ $ nv = $ this ->_applyDecimalScale ($ nv , strtolower ($ meta ['dbtype ' ] ?? ($ meta ['definition ' ] ?? ($ meta ['type_length ' ] ?? $ type ))));
5450+ }
5451+ $ allowed = is_numeric ($ nv );
5452+ $ value = $ nv ;
54435453 }
54445454 elseif (isset ($ meta ['enumValues ' ]) && is_array ($ meta ['enumValues ' ])) {
54455455 $ allowed = in_array ((string ) $ value , $ meta ['enumValues ' ], true );
@@ -5573,6 +5583,7 @@ protected function _secureValidateInsertValues(array $insertData, string $contex
55735583 $ validated = [];
55745584
55755585 foreach ($ insertData as $ col => $ val ) {
5586+ // 1) Spaltenname absichern
55765587 if (!$ this ->_secureIsSafeColumn ($ col )) {
55775588 $ this ->reset (true );
55785589 throw $ this ->handleException (
@@ -5581,13 +5592,13 @@ protected function _secureValidateInsertValues(array $insertData, string $contex
55815592 );
55825593 }
55835594
5584- // Subquery object ?
5595+ // 2) Subquery ?
55855596 if (is_object ($ val ) && method_exists ($ val , 'getSubQuery ' )) {
55865597 $ validated [$ col ] = $ val ;
55875598 continue ;
55885599 }
55895600
5590- // Special values like [F], [I], [N]
5601+ // 3) [F], [I], [N]
55915602 if (is_array ($ val ) && count ($ val ) === 1 ) {
55925603 $ key = array_key_first ($ val );
55935604
@@ -5612,18 +5623,21 @@ protected function _secureValidateInsertValues(array $insertData, string $contex
56125623 );
56135624 }
56145625
5615- $ validated [$ col ] = [ $ key => $ inner ] ;
5626+ $ validated [$ col ] = $ val ;
56165627 continue ;
56175628 }
56185629 }
56195630
5620- // Allow only scalar values
5621- if (!is_scalar ($ val ) && $ val !== null ) {
5622- $ this ->reset (true );
5623- throw $ this ->handleException (
5624- new \Exception ("Invalid non-scalar value for column ' {$ col }' in {$ context }() " ),
5625- $ context
5626- );
5631+ // Normalize decimals
5632+ $ meta = $ this ->_secureGetColumnMeta ($ col );
5633+ $ type = strtolower ($ meta ['type ' ] ?? '' );
5634+ $ dbType = strtolower ($ meta ['dbtype ' ] ?? ($ meta ['definition ' ] ?? ($ meta ['type_length ' ] ?? $ type )));
5635+
5636+ if (in_array ($ type , ['decimal ' , 'float ' , 'double ' , 'real ' ], true )) {
5637+ $ val = $ this ->_normalizeDecimalStringSimple ($ val );
5638+ if (is_string ($ val )) {
5639+ $ val = $ this ->_applyDecimalScale ($ val , $ dbType );
5640+ }
56275641 }
56285642
56295643 $ validated [$ col ] = $ val ;
@@ -6172,4 +6186,57 @@ public function clearLastQuery(): void
61726186 $ this ->_lastQuery = '' ;
61736187 $ this ->_lastDebugQuery = '' ;
61746188 }
6189+
6190+ /**
6191+ * Simple and safe decimal normalization for comma/dot formats.
6192+ * Examples: "12,34"->"12.34", "1.234,56"->"1234.56", "1,234.56"->"1234.56".
6193+ * Returns SQL-safe string with '.' or original value if unsure.
6194+ */
6195+ protected function _normalizeDecimalStringSimple (mixed $ value ): mixed
6196+ {
6197+ if ($ value === null ) return $ value ;
6198+ if (is_int ($ value ) || is_float ($ value )) {
6199+ $ s = rtrim (rtrim (sprintf ('%.15F ' , (float )$ value ), '0 ' ), '. ' );
6200+ return $ s === '' ? '0 ' : $ s ;
6201+ }
6202+ if (!is_string ($ value )) return $ value ;
6203+
6204+ $ s = trim ($ value );
6205+ if ($ s === '' ) return $ value ;
6206+
6207+ $ s = preg_replace ('/[ \x{00A0}\x{202F}]+/u ' , '' , $ s );
6208+
6209+ $ hasComma = strpos ($ s , ', ' ) !== false ;
6210+ $ hasDot = strpos ($ s , '. ' ) !== false ;
6211+
6212+ if ($ hasComma && !$ hasDot ) {
6213+ $ s = str_replace (', ' , '. ' , $ s );
6214+ } elseif ($ hasComma && $ hasDot ) {
6215+ if (strrpos ($ s , ', ' ) > strrpos ($ s , '. ' )) {
6216+ $ s = str_replace ('. ' , '' , $ s );
6217+ $ s = str_replace (', ' , '. ' , $ s );
6218+ } else {
6219+ $ s = str_replace (', ' , '' , $ s );
6220+ }
6221+ } else {
6222+ if (preg_match ('/^\d{1,3}(\.\d{3})+$/ ' , $ s )) {
6223+ $ s = str_replace ('. ' , '' , $ s );
6224+ }
6225+ }
6226+
6227+ if (!preg_match ('/^-?\d+(\.\d+)?$/ ' , $ s )) return $ value ;
6228+ return $ s ;
6229+ }
6230+ /**
6231+ * Format normalized decimal string to column scale (DECIMAL(p,s)) to avoid 1265.
6232+ */
6233+ protected function _applyDecimalScale (string $ normalized , string $ dbType ): string
6234+ {
6235+ if (preg_match ('/decimal\s*\(\s*\d+\s*,\s*(\d+)\s*\)/i ' , $ dbType , $ m )) {
6236+ $ scale = (int )$ m [1 ];
6237+ return number_format ((float )$ normalized , $ scale , '. ' , '' );
6238+ }
6239+ return $ normalized ;
6240+ }
6241+
61756242}
0 commit comments