So I have a spaghetti PHP app I am converting to using OOP database class / MVC layout
Previously, on a form, there was a sql statement being executed on post that would fetch the inserted ID into a bind variable (based on the SO Get the auto-generated ID after an insert
$sql ="insert into table (name, type) values (:name, :type) returning id into :id";
$squery = oci_parse($link, $sql);
oci_bind_by_name($squery,":name", $_POST['name']);
oci_bind_by_name($squery,":type", $_POST['type']);
oci_bind_by_name($squery,":id", $id,-1, SQLT_INT);
oci_execute($squery);
at this point, $id could be used as the variable has been assigned the inserted id
So, here is my issue, my model passes the values from the $_POST to the database class, but never assigns the variable
Model
function create(){
$sql ="insert into table (name, type) values (:name, :type) returning id into :id";
$this->db->execute($sql, "table insert",
array(
array(":name", $_POST['name'], -1, SQLT_CHR),
array(":type", $_POST['type'], -1, SQLT_CHR),
array(":id", $id,-1, SQLT_INT)
)
);
return $id;
}
Database Class( based on http://docs.oracle.com/cd/E17781_01/appdev.112/e18555/ch_three_db_access_class.htm )
public function execute($sql, $action, $bindvars = array()) {
$this->stid = oci_parse($this->conn, $sql);
if ($this->prefetch >= 0) {
oci_set_prefetch($this->stid, $this->prefetch);
}
foreach ($bindvars as $bv) {
// oci_bind_by_name(resource, bv_name, php_variable, length , type)
oci_bind_by_name($this->stid, $bv[0], $bv[1], $bv[2], $bv[3]);
}
oci_set_action($this->conn, $action);
oci_execute($this->stid); // will auto commit
}
The class is correct, meaning it will execute fine , it just will not return the last inserted id.
this is what i came up with (surely theres a better answer)
the issue was when i passed in the variable for returning into bind, it was not passing the variable as a variable ($id) it was passing it in as a variable value, and it hadnt been assigned so it was blank
so in my database class i created
public function executeAndReturnID($sql, $action, $bindvars = array(), $return) {
$this->stid = oci_parse($this->conn, $sql);
if ($this->prefetch >= 0) {
oci_set_prefetch($this->stid, $this->prefetch);
}
foreach ($bindvars as $bv) {
oci_bind_by_name($this->stid, $bv[0], $bv[1], $bv[2], $bv[3]);
}
// THIS PART TAKES THE RETURN AND CREATES A BIND AND VARIABLE
oci_bind_by_name($this->stid, ':'.$return, $res, -1, SQLT_INT);
oci_execute($this->stid); // will auto commit
$this->stid = null; // free the statement resource
return($res);
}
and in my Model
function create(){
$sql ="insert into table (name, type) values (:name, :type) returning id into :id";
$id = $this->db->executeAndReturnID($sql, "table insert",
array(
array(":name", $_POST['name'], -1, SQLT_CHR),
array(":type", $_POST['type'], -1, SQLT_CHR),
),
"id" //the bind var for return into
);
return $id;
}
Related
I have an array, which includes an insertId (as a key) several other values; this needs to be submitted as a $key=> $value array (e.g. 1 => 2, 1 =>3, 1=>5) etc.
However, when I bind the parameters within the foreach loop, I keep getting an array to string conversion error. as a result I get one row being inserted into the db (the correct key,and then a 0).
function instructorSubject()
{
$query = "INSERT into instructor_has_subject
SET instructor_id = :instructor_id,
subject_id = :id";
$last_id = $this->conn->lastInsertId();
$stmt = $this->conn->prepare($query);
//print_r($last_id);
//print_r($this->id);
if (isset($this->id) && $this->id != '') {
foreach ($_POST as $values) {
$stmt->bindParam(":instructor_id", $last_id, PDO::PARAM_INT);
$stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
}
if($stmt->execute())
{
return true;
}
else
{
var_dump($stmt);
print_r($stmt->errorInfo());
return false;
}
}
}
A sample array is something like this:
the insert id: 87
and then the second array appearing as a straight forward key=>value pair (for example:)
( [0] => 1 [1] => 3 )
I feel it has something to do with where I'm binding within the foreach. thanks in advance for any assistance.
After speaking to you in chat, this is the solution we came up with.
function instructorSubject()
{
$query = "INSERT INTO instructor_has_subject (instructor_id, subject_id) VALUES (?,?)";
$last_id = $this->conn->lastInsertId();
$stmt = $this->conn->prepare($query);
if(!empty($this->id)) {
foreach($_POST['subject_id'] as $id) {
$stmt->execute(array(
$last_id,
$id
));
}
}
}
The main thing we changed I believe was changing $_POST to $_POST['subject_id'].
We also removed bindParam completely from the function, instead opting for unnamed parameters and passing the variables via execute() inside the loop.
Relatively new to PDO (and OOP in general), my 3 named parameters are giving me an error. This is the function I have written to check if a value already exists in the database (so I won't have duplicate entries):
function checkTable($table, $column, $value, $con) {
$stmt = $con->prepare("SELECT * FROM :tbl WHERE :col = :val");
$stmt->execute(['tbl' => $table, 'col' => $column, 'val' => $value]);
return $stmt->fetchAll();
}
Of course $con is the PDO connection (yes I have checked, it is connected and I can run normal queries on the database)
I am calling the function with this piece of code:
checkTable("posts", "title", "title", $con);
I'm expecting to see true being returned, as the value I'm putting in does exist in the database, but all I'm getting is
'SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens'
EDIT: I've tested this outside a function, and this worked just as expected:
$bind = ['tbl' => "posts", 'col' => "title", 'val' => "title"];
$query = query("SELECT * FROM :tbl WHERE :col = :val", $con, $bind);
var_dump($query);
Where the query() function looks like this:
function query($query, $con, $bind = null) {
try {
$stmt = $con->prepare($query);
$stmt->execute($bind);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$result = $stmt->fetchAll();
return $result;
} catch(Exception $e) {
return false;
}
}
You cannot use table and column names for substitution within prepared statements. :tbl - is a table name. So you have only two tokens in your query :col, :val.
Also :col would be replaced with 'column_name' (with quotes). Where condition would be looks like 'column_name'='value'.
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!
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);
}
}
have you tried using exec versus query?
in the docs:
http://us1.php.net/manual/en/pdo.exec.php
versus
http://us1.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
Suppose I have a function
function fetchAll(){
$args = func_get_args();
$query = array_shift($args);
$query = str_replace("%s","'%s'",$query);
foreach ($args as $key => $val) {
$args[$key] = mysql_real_escape_string($val);
}
$query = vsprintf($query, $args);
if (!$query) return FALSE;
$res = mysql_query($query);
if (!$res) {
trigger_error("db: ".mysql_error()." in ".$query);
return FALSE;
}
$a = array();
while($row = mysql_fetch_assoc($res)) $a[]=$row;
return $a;
}
and then use it like this
$a=$db->fetchAll("SELECT * FROM users WHERE status=%s LIMIT %d,%d",$status,$start,$num);
How can I rewrite it using PDO?
Every example I can find shows only how to bind parameters directly. Should I pass variable type as well as it's value? Or make this call always 4 lines - 3 binds and execute?
edit: as the Colonel indicated, apparently this (no longer?) works with LIMIT clauses.
If you're using simple queries / are not that bothered with type:
function fetchAll(){
$args = func_get_args();
$query = array_shift($args);//'SELECT * FROM users WHERE status=? LIMIT ?,?'
//you'll need a reference to your PDO instance $pdo somewhere....
$stmt = $pdo->prepare($query);
$stmt->execute($args);
return $stmt->fetchAll();
}
Every example I can find shows only
how to bind parameters directly.
Should I pass variable type as well as
it's value? Or make this call always 4
lines - 3 binds and execute?
You don't have to fire binds one line at a time; you can bind with an array like this:
# the data we want to insert
$data = array('Cathy', '9 Dark and Twisty Road', 'Cardiff');
$STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?)");
$STH->execute($data);
Is there an easy way to echo the value stored in a bound parameter.
$sql ="call storedproc(:firstname, :lastname)";
$stmt = $this->DBH->prepare($sql);
$stmt->bindParam(':firstname', $fname);
$stmt->bindParam(':lastname', $lname);
//I want to do this
echo $stmt->firstname;
$stmt->execute;
If you only want to "see" what's going on then there's PDOStatement->debugDumpParams():
Dumps the informations contained by a prepared statement directly on the output. It will provide the SQL query in use, the number of parameters used (Params), the list of parameters, with their name, type (paramtype) as an integer, their key name or position, the value, and the position in the query (if this is supported by the PDO driver, otherwise, it will be -1).
<?php
function parms($string,$data) {
$indexed=$data==array_values($data);
foreach($data as $k=>$v) {
if(is_string($v)) $v="'$v'";
if($indexed) $string=preg_replace('/\?/',$v,$string,1);
else $string=str_replace(":$k",$v,$string);
}
return $string;
}
$string='INSERT INTO stuff(name,value) VALUES (?,?)';
$data=array('Fred',23);
$string='INSERT INTO stuff(name,value) VALUES (:name,:value)';
$data=array('name'=>'Fred','value'=>23);
print parms($string,$data);
$update = "update registration";
$update .= " set status=:statusid";
$update .= " where refid=:refid";
$stmt = $db->prepare($update);
$data=array('statusid' => $statusid,'refid' => $refid);
echo parms($update,$data); // checking bind values
exit;
$stmt->execute(array ('statusid' => $statusid,'refid' => $refid));
?>
//output
update registration set status='all' where refid='45'