- Version: 1
- RFC Started: 2022-04-21
- RFC Updated: 2022-05-11
- Voting Start: 2022-05-11 15:00 UTC / 16:00 BST
- Voting End: 2022-05-25 15:00 UTC / 16:00 BST
- Author: Kamil Tekiela, and Craig Francis [craig#at#craigfrancis.co.uk]
- Status: Accepted
- First Published at: https://wiki.php.net/rfc/mysqli_execute_query
- GitHub Repo: https://github.com/craigfrancis/php-mysqli-execute-query-rfc
- Implementation: From Kamil Tekiela (proof of concept)
Make parameterised MySQLi queries easier, with mysqli_execute_query($sql, $params)
.
This will further reduce the complexity of using parameterised queries - making it easier for developers to move away from mysqli_query()
, and the dangerous/risky escaping of user values.
This new function is a simple combination of:
mysqli_prepare()
mysqli_execute()
mysqli_stmt_get_result()
It follows the original Draft RFC MySQLi Execute with Parameters, which proposed a single function to execute a parameterised query; and the Implemented RFC mysqli bind in execute, which addressed the difficulties with bind_param()
.
Using an example, assume we start with:
$db = new mysqli('localhost', 'user', 'password', 'database');
$name = '%a%';
$type1 = 1; // Admin
$type2 = 2; // Editor
Traditionally someone might use escaping, which is very error prone, e.g.
foreach ($db->query('SELECT * FROM user WHERE name LIKE "' . $db->real_escape_string($name) . '" AND type_id IN (' . $db->real_escape_string($type1) . ', ' . $db->real_escape_string($type2) . ')') as $row) { // INSECURE
print_r($row);
}
To avoid mistakes, parameterised queries should be used (with a literal-string), but can be fairly complex:
$statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)');
$statement->bind_param('sii', $name, $type1, $type2);
$statement->execute();
foreach ($statement->get_result() as $row) {
print_r($row);
}
Since PHP 8.1, we no longer have problems with binding by reference, or needing to specify the variable types via the first argument to bind_param()
, e.g.
$statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)');
$statement->execute([$name, $type1, $type2]);
foreach ($statement->get_result() as $row) {
print_r($row);
}
This proposed function will simplify this even further, by allowing developers to write this in a one line foreach:
foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) {
print_r($row);
}
In pseudo-code it's basically:
function mysqli_execute_query(mysqli $mysqli, string $sql, array $params = null)
{
$driver = new mysqli_driver();
$stmt = $mysqli->prepare($sql);
if (!($driver->report_mode & MYSQLI_REPORT_STRICT) && $mysqli->error) {
return false;
}
$stmt->execute($params);
if (!($driver->report_mode & MYSQLI_REPORT_STRICT) && $stmt->error) {
return false;
}
return $stmt->get_result();
}
The name was inspired by Doctrine\DBAL\Connection::executeQuery().
The implementation is effectively calling mysqli_stmt_get_result() last. While it will return false
on failure, it will also return false
for queries that do not produce a result set (e.g. UPDATE
). Historically this has been addressed by using mysqli_errno()
, but since 8.1 the Change Default mysqli Error Mode RFC was accepted, and Exceptions are used by default.
Because mysqli_stmt is not returned, it's not possible to use its properties directly:
- int|string $affected_rows - use
$mysqli->affected_rows
ormysqli_affected_rows($mysqli)
- int|string $insert_id - use
$mysqli->insert_id
ormysqli_insert_id($mysqli)
- int|string $num_rows - also available on
mysqli_result
- int $param_count
- int $field_count - also available on
mysqli_result
- int $errno - use
mysqli_errno($mysqli)
,$mysqli->errno
- string $error - use
mysqli_error($mysqli)
,$mysqli->error
- array $error_list - use
mysqli_error_list($mysqli)
,$mysqli->error_list
- string $sqlstate - use
mysqli_sqlstate($mysqli)
,$mysqli->sqlstate
- int $id
It's also worth noting the error property usage will hopefully reduce, as more developers use mysqli_sql_exception
for errors (because the mysqli Error Mode now defaults to MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT
).
The implementation discards the mysqli_stmt
object immediately, so you cannot re-issue a statement with new parameters. Anyone who would benefit from this (to skip running prepare again), can still use mysqli_prepare()
.
Cannot change mysqli_query()
because its second argument is $resultmode
.
Cannot replace the deprecated mysqli_execute()
function, which is an alias for mysqli_stmt_execute()
, because it would create a backwards compatibility issue.
Because the Remove support for libmysql from mysqli RFC has been accepted, it makes it much easier to implement with mysqlnd
.
None
PHP 8.2
None known
- mysqli, adding a new function.
None known
None
None
None
N/A
N/A
Accept the RFC
* Yes * NoFrom Kamil Tekiela (proof of concept)
This implementation copies some details to the mysqli object, but not the affected rows. This means mysqli_affected_rows($mysqli)
and $mysqli->affected_rows
will currently return -1.
N/A
None