diff --git a/ext/pdo_mysql/mysql_statement.c b/ext/pdo_mysql/mysql_statement.c index 765e05e1358..e3127f95d69 100644 --- a/ext/pdo_mysql/mysql_statement.c +++ b/ext/pdo_mysql/mysql_statement.c @@ -27,6 +27,7 @@ #include "pdo/php_pdo_driver.h" #include "php_pdo_mysql.h" #include "php_pdo_mysql_int.h" +#include "zend_interfaces.h" #ifdef PDO_USE_MYSQLND # define pdo_mysql_stmt_execute_prepared(stmt) pdo_mysql_stmt_execute_prepared_mysqlnd(stmt) @@ -490,7 +491,14 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da case IS_DOUBLE: mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_DOUBLE); break; + case IS_OBJECT: + if(zend_class_implements_interface(Z_OBJCE_P(parameter), zend_ce_stringable)) { + mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_VAR_STRING); + break; + } + ZEND_FALLTHROUGH; default: + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a scalar value or null"); PDO_DBG_RETURN(0); } @@ -530,7 +538,19 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da b->buffer = &Z_DVAL_P(parameter); PDO_DBG_RETURN(1); + case IS_OBJECT: + if(zend_class_implements_interface(Z_OBJCE_P(parameter), zend_ce_stringable)) { + convert_to_string(parameter); + b->buffer_type = MYSQL_TYPE_STRING; + b->buffer = Z_STRVAL_P(parameter); + b->buffer_length = Z_STRLEN_P(parameter); + *b->length = Z_STRLEN_P(parameter); + PDO_DBG_RETURN(1); + } + ZEND_FALLTHROUGH; + default: + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a scalar value or null"); PDO_DBG_RETURN(0); } #endif /* PDO_USE_MYSQLND */ diff --git a/ext/pdo_mysql/tests/gh13384.phpt b/ext/pdo_mysql/tests/gh13384.phpt new file mode 100644 index 00000000000..db3e3153e03 --- /dev/null +++ b/ext/pdo_mysql/tests/gh13384.phpt @@ -0,0 +1,81 @@ +--TEST-- +GH-13384 Fixed GH-13167 Fixed the behavior when an inappropriate value was passed to `bindValue` and `bindParam`. +--EXTENSIONS-- +pdo_mysql +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + +$stringableObject = new class () implements Stringable { + public function __toString(): string + { + return '555'; + } +}; + +echo "Stringable object, value set after bindParam:\n"; +try { + $stmt = $db->prepare('SELECT ?'); + $param = 'foo'; + $stmt->bindParam(1, $param, PDO::PARAM_STR); + $param = $stringableObject; + $stmt->execute(); + var_dump(is_object($param), $param === $stringableObject); + echo "\n"; +} catch (Throwable $e) { + echo $e->getMessage()."\n\n"; +} + +echo "Stringable object, bindValue:\n"; +$stmt = $db->prepare('SELECT (?)'); +$stmt->bindValue(1, $stringableObject, PDO::PARAM_INT); +$stmt->execute(); +var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); +echo "\n"; + +echo "Normal object, bindValue:\n"; +try { + $stmt = $db->prepare('SELECT (?)'); + $stmt->bindValue(1, new stdClass(), PDO::PARAM_INT); + $stmt->execute(); +} catch (Throwable $e) { + echo $e->getMessage()."\n\n"; +} + +echo "Array, bindParam:\n"; +try { + $stmt = $db->prepare('SELECT (?)'); + $param = ['aaa']; + $stmt->bindParam(1, $param, PDO::PARAM_INT); + $stmt->execute(); +} catch (Throwable $e) { + echo $e->getMessage(); +} +?> +--EXPECT-- +Stringable object, value set after bindParam: +bool(true) +bool(true) + +Stringable object, bindValue: +array(1) { + [0]=> + array(1) { + ["?"]=> + string(3) "555" + } +} + +Normal object, bindValue: +SQLSTATE[HY105]: Invalid parameter type: Expected a scalar value or null + +Array, bindParam: +SQLSTATE[HY105]: Invalid parameter type: Expected a scalar value or null