I am making a function to more easily use prepared statements in sql queries. But while working on it, I ran into a strange bug.
When you run the code below the first var_dump
of $response
prints the $stmt
variable like I expect it to, but after closing the $stmt
it give a lot of Warnings and prints a NULL filled version of $stmt
. I only make a copy of $stmt as $response
, so I wouldn’t expect $response
to change when closing $stmt
.
Could someone explain why this happens and how I can prevent it?
function sql_bug() { global $dbc; // Database connection $sql = "UPDATE users SET username = 'J0R1AN' WHERE id = 1"; // Test query if ($stmt = $dbc->prepare($sql)) { if ($stmt->execute()) { $response = $stmt->get_result(); if ($response === false) { // Checks for e.g. UPDATE queries that return bool(false) instead of a mysqli_result, // and sets $response to $stmt instead, to return more valuable information when using UPDATE $response = $stmt; } var_dump($response); // Prints expected $stmt variable $stmt->close(); // Somehow changes $response? var_dump($response); // Prints $stmt variable filled with NULLS return $response; } } return false; }
Advertisement
Answer
Variable assignment does not make a new copy of an object in PHP. To create a copy you would need to use clone
. Simple example:
$obj = (object) ['a'=>42]; $new_obj = $obj; $obj->a = 100; var_dump($new_obj); // outputs // stdClass Object // ( // [a] => 100 // )
You have fallen victim to multiple fallacies.
- You do not need to
close
anything. PHP will do it for you. Usingclose
only complicates a lot of things. - Don’t check return value of
prepare
orexecute
functions. Instead enable mysqli error reporting. How to get the error message in MySQLi? - Don’t use global objects or limit their use to the absolute minimum.
- You don’t need to expose
mysqli_stmt
ormysqli_result
objects outside of your function. Once you perform the statement and get the data you can discard them.
If we wanted to fix this function properly we could do something like this:
function sql_bug(mysqli $dbc, string $sql, array $params = []): ?array { $stmt = $dbc->prepare($sql); if ($params) { // bind optional parameters if the query has variable input $stmt->bind_param(str_repeat("s", count($params)), ...$params); } $stmt->execute(); $response = $stmt->get_result(); if ($response) { // If the query returned results then fetch them into an array and return it return $response->fetch_all(MYSQLI_BOTH); } // return nothing if the query was successfully executed and it didn't produce results return null; }
The above function is a generic function that can handle any SQL statement with and without parameters. It will not return anything if the query was INSERT or UPDATE and if it was SELECT it will return the data in an array. No need to copy or return the inner objects. You are writing a function to abstract from the mysqli innards.