Skip to content
Advertisement

PHP: mysqli_stmt->close() affecting earlier copy of stmt

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.

  1. You do not need to close anything. PHP will do it for you. Using close only complicates a lot of things.
  2. Don’t check return value of prepare or execute functions. Instead enable mysqli error reporting. How to get the error message in MySQLi?
  3. Don’t use global objects or limit their use to the absolute minimum.
  4. You don’t need to expose mysqli_stmt or mysqli_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.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement