Вопрос

Finally getting around to learning prepared statements. I'm getting a maddening error when trying to run a pair of simple inserts:

$p_stmt = $mysqli->prepare("INSERT INTO ww_pages (page_key) VALUES (?)");
$p_stmt->bind_param('s', $page_key);
$p_stmt->execute();
        
$pv_stmt = $mysqli->prepare("INSERT INTO ww_page_versions (page_id, page_title, page_content, version_notes, version_timestamp) VALUES (?, ?, ?, ?, ?)");
$pv_stmt->bind_param('issss', $p_stmt->insert_id, $page_title, trim($_POST["page_content"]), trim($_POST["version_notes"]), date("Y-m-d H:i:s"));
$pv_stmt->execute();
echo $pv_stmt->error;

The echo $pv_stmt->error; gives this error: Column 'page_id' cannot be null

As I'm sure you can interpret, I'm trying to assign page_id the insert_id of the first statement. I'm 100% sure that this value is non-null, and returns an integer value. I tested it directly with this:

echo "NEW ID: ".$p_stmt->insert_id."::".is_int($p_stmt->insert_id);

The output? NEW ID: 13::1

What am I doing wrong? Why am I getting "column cannot be null" when the column isn't null? The only solutions I can find online are involving unexpectedly null values.

Это было полезно?

Решение 2

Put the value of insert_id into a temporary variable and bind that variable instead.

$p_stmt = $mysqli->prepare("INSERT INTO ww_pages (page_key) VALUES (?)");
$p_stmt->bind_param('s', $page_key);
$p_stmt->execute();
$insert_id = $p_stmt->insert_id;

$pv_stmt = $mysqli->prepare("INSERT INTO ww_page_versions (page_id, page_title, page_content, version_notes, version_timestamp) VALUES (?, ?, ?, ?, ?)");
$pv_stmt->bind_param('issss', $insert_id, $page_title, trim($_POST["page_content"]), trim($_POST["version_notes"]), date("Y-m-d H:i:s"));
$pv_stmt->execute();

Другие советы

The solution to this problem is to save the value of the property $p_stmt->insert_id in a variable and bind that variable instead. You have to do the same for trim() and date() anyway, albeit for different reasons.

$insert_id = $p_stmt->insert_id;
$page_content = trim($_POST["page_content"]);
$version_notes = trim($_POST["version_notes"]);
$date = date("Y-m-d H:i:s");
$pv_stmt->bind_param('issss', $insert_id, $page_title, $page_content, $version_notes, $date);

As of PHP 8.1, you can also bind by value when you pass all values as an array to execute(). No temporary variables are needed.

$pv_stmt = $mysqli->prepare("INSERT INTO ww_page_versions (page_id, page_title, page_content, version_notes, version_timestamp) VALUES (?, ?, ?, ?, ?)");
$pv_stmt->execute([$p_stmt->insert_id, $page_title, trim($_POST["page_content"]), trim($_POST["version_notes"]), date("Y-m-d H:i:s")]);

Explanation of why binding insert_id doesn't work

The reason why binding mysqli_stmt::insert_id results in a value NULL becomes a little bit more clear on PHP 8.1. Since this version, PHP has added property types to most of built-in classes, even for properties that aren't true properties but are __get() calls instead. With PHP 8.1 you get the following error:

Fatal error: Uncaught Error: Cannot access uninitialized non-nullable property mysqli_stmt::$insert_id by reference

PHP claims that the property is uninitialized and you can't have a reference to an uninitialized property. You can mimick the same behaviour with this code:

class A {
    public int $insert_id;
}

$a = new A();

$stmt = $mysqli->prepare('SELECT ?');
$stmt->bind_param('s', $a->insert_id);
$stmt->execute();

For the same reason, assignment by reference to a variable would not work:

$foo =& $p_stmt->insert_id;

But you might ask, how can the property be uninitialized when you can read its value without any issue. The answer is because internally these properties are implemented using function calls in PHP, similar to __get() magic method. The value is read from the internal memory of mysqlnd, not from the property itself. Properties of mysqli are just a facade to the underlying client library mysqlnd.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top