Reputation: 541
I have a table of contacts with the following fields/data types: contactid
(PK, auto-increment, not null), fname varchar(50)
, lname varchar(50)
, email varchar(50)
, is_member tinyint(1)
(i.e., boolean), and createdate date
.
The data is submitted to the same PHP page that the form is on, and the values of the $_POST
array are packaged into a Contact object and passed to the following function:
<?php
public static function insertContact(Model $model, Contact $contact) {
$database = new Database();
$sql = 'INSERT INTO contact (fname, lname, email, is_member, createdate) VALUES (:fname, :lname, :email, :is_member, CURDATE())';
$params = array(
':fname' => $contact->getFirstname(),
':lname' => $contact->getLastname(),
':email' => $contact->getEmail(),
':is_member' => intval($contact->isMember())
);
$result = $database->query($sql, $params, 'LASTID'); // Will return last inserted ID.
$model->notify('Added contact ID ' . strval($result) . ' to the database.');
}
?>
This function is called if another function that checks to see if the contact already exists returns false
. I'm using PDO prepared statements, and everything's working fine for SELECT
statements, but when I try to execute this INSERT
statement, I'm running into problems. The $params
array is correct, and the number of params matches the number of placeholders, but the is_member
field is causing problems. If the value is 1
, the INSERT
completes, but the resulting row looks something like this:
contactid fname lname email is_member createdate 14 1 1 1 1 2014-05-31
Has anybody seen this behavior before? Moreover, if the value of is_member
is 0
, then the query fails entirely with the PDOStatement warning [HY093]: Invalid parameter number: number of bound variables does not match number of tokens
. What I don't get is that $params
has the correct number of values to bind to the placeholder tokens.
[UPDATE:] The $database->query()
method is as follows:
<?php
// Return type is the format in which to return the result of the query.
public function query($sql, $params, $returnType) {
if($stmt = $this->connection->prepare($sql)) {
foreach($params as $key => $value) {
if($value != 'noParams') {
$stmt->bindParam($key, $value);
}
}
if($stmt->execute()) { // execute() returns TRUE on success
switch($returnType) {
case 'ASSOC':
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
break;
case 'LASTID':
$result = $this->connection->lastInsertId();
break;
// ... more cases, etc.
}
} else {
$result = null;
die('Error: ' . $this->connection->error);
}
return $result;
} else {
die('Prepare failed: (' . $this->connection->error . ')');
}
}
?>
As mentioned, this query()
method works for prepared statements for SELECT
operations, however it is exhibiting the aforementioned behavior when trying to do a simple INSERT
. Any help is appreciated!! Thanks!
Upvotes: 0
Views: 754
Reputation: 212452
The first problem:
if($value != 'noParams') {
If value is 0, then this will be a false match because it's a loose-typed comparison; so "falsey" type values (null
, 0.0
, false
, etc) will give you problems, because it won't bind any falsey values from your array... which explains your Invalid parameter number: number of bound variables does not match number of tokens
error....
make it a strict comparison:
if($value !== 'noParams') {
Your second problem:
Quoting from the PHP Docs
Unlike PDOStatement::bindValue(), the variable is bound as a reference and will only be evaluated at the time that PDOStatement::execute() is called.
At the point where you actually execute the statement, $value
only has the value from the last iteration of your binding loop, which is the value from the is_member
array element, which is 1
. That's why you're getting nothing but 1
values for all your bind variables
Change your foreach
loop to
foreach($params as $key => &$value) {
So that $value
is "by reference", and it should then be the correct reference that is bound to each bind var
Alternatively, use bindValue()
rather than bindParam()
Holistic:
So for the complete fix
foreach($params as $key => &$value) {
if($value !== 'noParams') {
$stmt->bindParam($key, $value);
}
}
Upvotes: 2
Reputation: 224
have you tried using exec versus query?
in the docs: https://www.php.net/manual/en/pdo.exec.php
versus https://www.php.net/pdo.query
bottom line: PDO::query — Executes an SQL statement, returning a result set as a PDOStatement object while PDO::exec — Execute an SQL statement and return the number of affected rows
Upvotes: 0