so far I've come to this.
My goal is to be able to apply optional search filters such as a.chainid and a.branchid if needed, for that to happen I need dynamic bind_param.
The code seems to be fine, but in fact it's not working for some reason.
Care to tell me what's wrong?
The fetch returns me nothing instead of 5 rows that should.
Thanks in advance
$sql = "SELECT a.COUPONID, a.TRUSTANDUSEID FROM `custom_redemptions` a WHERE a.couponid = 3";
$types = '';
$params = array(&$types);
if ($branchid != null) {
$sql .= "AND a.branchid = ?";
$types .= 's';
$params[] = $branchid;
}
if ($chainid != null) {
$sql .= "AND a.chainid = ?";
$types .= 's';
$params[] = $chainid;
}
if ($stmt = $this->dbCon->prepare($sql)) {
call_user_func_array(array($stmt, 'bind_param'), $params);
$stmt->execute();
$stmt->bind_result($couponid, $trustanduseid);
while ($stmt->fetch()) { echo $couponid; }
$stmt->close();
}
This solution might help you
$sql = "SELECT a.COUPONID, a.TRUSTANDUSEID FROM `custom_redemptions` a WHERE a.couponid = 3";
$types = '';
$params = array(&$types);
if ($branchid != null) {
$sql .= " AND a.branchid = ?";
$types .= 's';
$params[] = $branchid;
}
if ($chainid != null) {
$sql .= " AND a.chainid = ?";
$types .= 's';
$params[] = $chainid;
}
if ($stmt = $this->dbCon->prepare($sql)) {
call_user_func_array(array($stmt, 'bind_param'), $params);
$stmt->execute();
$stmt->bind_result($couponid, $trustanduseid);
while ($stmt->fetch()) { echo $couponid; }
$stmt->close();
}
Finally what was needed was &$chainid instead of $chainid.
Spent 8 hours researching about it. LOL
Related
I'm trying to convert a complex php file I made a year ago over to prepared statements. Parameters will be passed in like so:
file.php?name=John&age=20
However there could be many more parameters that are potentially used. job, address, phone number, etc etc. This is where I tend to get confused with prepared statements.
For example:
$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
if (isset($_REQUEST['name'])) {
$query .= " and name = ?";
}
if (isset($_REQUEST['age'])) {
$query .= " and age = ?";
}
if (isset($_REQUEST['address'])) {
$query .= " and address = ?";
}
$stmt = $db->prepare($query);
$stmt->bind_param('sis', $_REQUEST['name'], $_REQUEST['age'], $_REQUEST['address']);
$stmt->execute();
The issue here is bind_param because I don't know how many parameters could potentially be available.
How would I go about this in a logical manner?
A very good question. And the solution is very simple.
What you need is called argument unpacking operator. It will allow you to use an array of arbitrary length with bind_param. All you need is to collect accepted parameters into array, then create the types string dynamically and finally use the aforementioned operator:
$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
$params = array();
if (isset($_REQUEST['name'])) {
$query .= " and name = ?";
$params[] = $_REQUEST['name'];
}
if (isset($_REQUEST['age'])) {
$query .= " and age = ?";
$params[] = $_REQUEST['age'];
}
if (isset($_REQUEST['address'])) {
$query .= " and address = ?";
$params[] = $_REQUEST['address'];
}
if ($params)
$stmt = $db->prepare($query);
$types = str_repeat("s", count($params));
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $db->query($query);
}
Well, the process is going to be quite similar to how you build up your $query variable - i.e. you add to the parameters list one at a time.
Consider that bind_param requires two things:
First is a list of the data types as a simple string. So you can just have a string variable which you add to for each parameter, and then pass that to bind_param at the end.
Second is a list of parameters. This is trickier, because you can't just supply an array to bind_param. However, you can create an array of your own, and then use call_user_func_array to convert that to a flat list.
Here's one I wrote earlier (and works nicely). Note that it attempts to detect the parameter types and create a suitable string, but you could just build up a string manually in your if statements if you prefer:
$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
$params = array();
if (isset($_REQUEST['name'])) {
$query .= " and name = ?";
$params[] = $_REQUEST['name'];
}
if (isset($_REQUEST['age'])) {
$query .= " and age = ?";
$params[] = $_REQUEST['age'];
}
if (isset($_REQUEST['address'])) {
$query .= " and address = ?";
$params[] = $_REQUEST['address'];
}
$stmt = $db->prepare($query);
if (!is_null($params))
{
$paramTypes = "";
foreach ($params as $param)
{
$paramTypes .= mysqliContentType($param);
}
$bindParamArgs = array_merge(array($paramTypes), $params);
//call bind_param using an unpredictable number of parameters
call_user_func_array(array(&$stmt, 'bind_param'), getRefValues($bindParamArgs));
}
$stmt->execute();
function mysqliContentType($value)
{
if(is_string($value)) $type = 's';
elseif(is_float($value)) $type = 'd';
elseif(is_int($value)) $type = 'i';
elseif($value == null) $type = 's'; //for nulls, just have to assume a string. hopefully this doesn't mess anything up.
else throw new Exception("type of '".$value."' is not string, int or float");
return $type;
}
function getRefValues($arr)
{
$refs = array();
foreach($arr as $key => $value)
{
$refs[$key] = &$arr[$key];
}
return $refs;
}
Trying to get a function working to create simple CRUD "Select" with multiple parameters to any table. I think I got the hardest part, but couldn't fetch the data right now. Maybe I'm doing something wrong I can't figure out.
My prepared statement function:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
if($stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
} else {
$error = $mysqli->errno . ' ' . $mysqli->error;
error_log($error);
}
}
The query creator:
function create_select_query($table, $condition = "", $sort = "", $order = " ASC ", $clause = ""){
$table = escape_mysql_identifier($table);
$query = "SELECT * FROM ".$table;
if(!empty($condition)){
$query .= create_select_query_where($condition,$clause);
}
if(!empty($sort)){
$query .= " ORDER BY ".$sort." $order";
}
return $query;
}
The helper function to create the WHERE clause:
function create_select_query_where($condition,$clause){
$query = " WHERE ";
if(is_array($condition)){
$pair = array();
$size = count($condition);
$i = 0;
if($size > 1){
foreach($condition as $field => $val){
$i++;
if($size-1 == $i){
$query .= $val." = ? ".$clause. " ";
}else{
$query .= $val." = ? ";
}
}
}else{
foreach($condition as $field => $val){
$query .= $val." = ? ";
}
}
}else if(is_string($condition)){
$query .= $condition;
}else{
$query = "";
}
return $query;
}
The select function itself:
function crud_select($conn, $table, $args, $sort, $order, $clause){
$sql = create_select_query($table, array_keys($args),$sort, $order, $clause);
print_r($sql);
if($stmt = prepared_query($conn, $sql, array_values($args))){
return $stmt;
}else{
$errors [] = "Something weird happened...";
}
}
When I create the query, it seems to be OK but can't fetch the data. If I create an array with only one argument the query translates into:
SELECT * FROM `teste_table` WHERE id = ?
If I create with multiple parameters, it turns like this:
SELECT * FROM `teste_table` WHERE id = ? AND username = ?
So, how can I properly fetch the data from the select. This should be used for multiple purposes, so I could get more than one result, so the best way would be fetch data as array I guess.
I guess I'm close, but can't figure it out. Thanks
I told you to limit your select function to a simple primary key lookup. And now you opened a can of worms. As a result you are getting entangled implementation code and unreadable application code.
$table, $args, $sort, $order, $clause
What all these variables are for? How you're going to call this function - a list of gibberish SQL stubs in a random order instead of plain and simple SQL string? And how to designate a list of columns to select? How to use JOINS? SQL functions? Aliases? Why can't you just write a single SQL statement right away? You already have a function for selects, though without this barbaric error reporting code you added to it:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
}
Just stick to it and it will serve you all right.
$sql = "SELECT * FROM `teste_table` WHERE id = ? AND username = ?";
$stmt = prepared_query($mysqli, $sql, [$id, $name]);
$row = $stmt->get_result()->fetch_assoc();
The only specific select function could be, again, a simple primary key lookup:
function crud_find($conn, $table, $id)
{
$table = escape_mysql_identifier($table);
$sql = "SELECT * FROM $table WHERE id=?";
$stmt = prepared_query($conn, $sql, [$id], "i");
return $stmt->get_result()->fetch_assoc();
}
And for the everything else just use a generic function with native SQL.
I have a function to update up to 3 fields in a mysql table. The function can receive all 3 fields to be updated or just 1 or 2
Right now I am doing it like this (it works) to construct MySQL statement.
if ($foo1){
$mysql_set = '`foo1` = :foo1';}
if ($foo2){
if ($mysql_set){$mysql_set .= ', ';}
$mysql_set .= '`foo2` = :foo2';}
if ($foo3){
if ($mysql_set){$mysql_set .= ', ';}
$mysql_set .= '`foo3` = :foo3';}
$update = $db->prepare("UPDATE `bar` SET $mysql_set WHERE `id` = :id");
if ($foo1){
$update->bindValue(':foo1', $foo1, PDO::PARAM_STR);}
if ($foo2){
$update->bindValue(':foo2', $foo2, PDO::PARAM_STR);}
if ($foo3){
$update->bindValue(':foo3', $foo3, PDO::PARAM_STR);}
$update->bindValue(':id', $id, PDO::PARAM_INT);
$update->execute();
As you can see I am repeating "if ($foo1 - $foo3){}" twice to construct this MySQL query. It looks redundant and wondering if there's a better way to handle this scenario.
You can give an associative array to execute(), instead of calling bindValue() separately for each parameter.
$param_array = array(':id' => $id);
$set_array = array();
if ($foo1) {
$param_array[':foo1'] = $foo1;
$set_array[] = "foo1 = :foo1";
}
if ($foo2) {
$param_array[':foo2'] = $foo2;
$set_array[] = "foo2 = :foo2";
}
if ($foo3) {
$param_array[':foo3'] = $foo3;
$set_array[] = "foo3 = :foo3";
}
if (!empty($set_array)) {
$set_string = implode(", ", $set_array);
$sql = "UPDATE bar SET $set_string WHERE id = :id";
$update = $db->prepare($sql);
$update->execute($param_array);
}
Try.
if ($foo1){
$mysql_set = '`foo1` = :foo1';
$update = prepareStmt($db, $mysql_set);
$update->bindValue(':foo1', $foo1, PDO::PARAM_STR);
}
if ($foo2){
if ($mysql_set){$mysql_set .= ', ';}
$mysql_set .= '`foo2` = :foo2';
$update = prepareStmt($db, $mysql_set);
$update->bindValue(':foo2', $foo2, PDO::PARAM_STR);
}
if ($foo3){
if ($mysql_set){$mysql_set .= ', ';}
$mysql_set .= '`foo3` = :foo3';
$update = prepareStmt($db, $mysql_set);
$update->bindValue(':foo3', $foo3, PDO::PARAM_STR);
}
$update->bindValue(':id', $id, PDO::PARAM_INT);
$update->execute();
function prepareStmt($db,$mysql_set){
return $db->prepare("UPDATE `bar` SET $mysql_set WHERE `id` = :id");
}
I have to build up a sql statement and the params from a $place object that has a variable number of properties. When I use prepared sql statements by building the sql statement and params the long and bad practice way it works (returns all the rows from the database that it should):
<?
function buildSQLWhereClause($query, $conn, $place) {
if ($place['suburb']){
if($place['city'] && $place['province'] && $place['country']) {
$query .= "s.country = ? and
s.province = ? and
s.city = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ssss", $place['country'], $place['province'], $place['city'], $place['suburb']);
} else if ($place['province'] && $place['country']) {
$query .= "s.country = ? and
s.province = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("sss", $place['country'], $place['province'], $place['suburb']);
} else if ($place['city'] && $place['province']) {
$query .= "s.province = ? and
s.city = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("sss", $place['province'], $place['city'], $place['suburb']);
} else if ($place['city'] && $place['country']) {
$query .= "s.country = ? and
s.city = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("sss", $place['country'], $place['city'], $place['suburb']);
} else if ($place['city']) {
$query .= "s.city = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $place['city'], $place['suburb']);
} else if ($place['province']) {
$query .= "s.province = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $place['province'], $place['suburb']);
} else if ($place['country']) {
$query .= "s.country = ? and
s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $place['country'], $place['suburb']);
} else {
$query .= "s.suburb = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("s", $place['suburb']);
}
//////////////////////////// NO SUBURB ///////////////////////////////////////////////////
} else if ($place['city']) {
if ($place['province'] && $place['country']) {
$query .= "s.country = ? and
s.province = ? and
s.city = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("sss", $place['country'], $place['province'], $place['city']);
} else if ($place['province']) {
$query .= "s.province = ? and
s.city = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $place['province'], $place['city']);
} else if ($place['country']) {
$query .= "s.country = ? and
s.city = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $place['country'], $place['city']);
} else {
$query .= "s.city = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("s", $place['city']);
}
//////////////////////// NO SUBURB OR CITY ////////////////////////////////////////////////////////
} else if ($place['province']) {
if ($place['country']) {
$query .= "s.country = ? and
s.province = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $place['country'], $place['province']);
} else {
$query .= "s.province = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("s", $place['province']);
}
//////////////////////////////// NO SUBURB, CITY, OR PROVINCE ///////////////////////////////
} else if ($place['country']) {
$query .= "s.country = ?";
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param("s", $place['country']);
}
return $stmt;
}
function queryDbForProducts($conn, $place)
{
$query = "SELECT p.*, s.*
FROM product p
INNER JOIN product_shop ps
ON ps.p_id = p.p_id
INNER JOIN shop s
ON s.s_id = ps.s_id
WHERE ";
$stmt = buildSQLWhereClause($query, $conn, $place);
$stmt->execute();
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$parameters[] =& $row[$field->name];
}
When I use sql prepared statements by building up the sql statement and params the much better way, it doesn't work:
<?
function buildSQLWhereClause($place) {
$query = "SELECT p.*, s.* FROM product p INNER JOIN product_shop ps ON ps.p_id = p.p_id INNER JOIN shop s ON s.s_id = ps.s_id WHERE ";
$queryParams = [];
$queryParamTypes = "";
$i = 0;
$len = count($place);
foreach ($place as $key => $value) {
if ($i == $len - 1) {
$query .= "$key = ?";
$queryParams[] = $value;
$queryParamTypes .= "s";
} else {
$query .= "$key = ? AND ";
$queryParams[] = $value;
$queryParamTypes .= "s";
}
$i++;
}
return array(
"query" => $query,
"queryParams" => $queryParams,
"queryParamTypes" => $queryParamTypes
);
}
function queryDbForProducts($conn, $place)
{
$queryObject = buildSQLWhereClause($place);
$query = $queryObject['query'];
$queryParams = $queryObject['queryParams'];
$queryParamTypes = $queryObject['queryParamTypes'];
// prepare and bind
$stmt = $conn->prepare($query);
$stmt->bind_param($queryParamTypes, $queryParams);
$stmt->execute();
$meta = $stmt->result_metadata();
Hovering over the $stmt in the debugger shows:
affected_rows:-1
insert_id:0
num_rows:0
param_count:4
field_count:13
errno:2031
error:"No data supplied for parameters in prepared statement"
error_list:array(1)
sqlstate:"HY000"
id:1
No data supplied? Hovering over the $queryParams parameter in the debugger shows:
0:"Grey Lynn"
1:"Auckland"
2:"Auckland"
3:"New Zealand"
So I did provide the query parameters to the $stmt->bind_param() function. Did I provide them in the wrong format?
Hovering over $QueryParamTypes shows:
"ssss"
Hovering over $query shows:
"SELECT p.*, s.* FROM product p INNER JOIN product_shop ps ON ps.p_id = p.p_id INNER JOIN shop s ON s.s_id = ps.s_id WHERE suburb = ? AND city = ? AND province = ? AND country = ?"
How come it works when done with the code at the top of the question and it doesn't work when done with the code without all the is statements?
bind_param does not take an array as an argument, it takes varargs. You will need to use call_user_func_array if you want to call it with a dynamic number of arguments.
i.e.
$params = array_unshift($queryParams, $queryParamTypes);
call_user_func_array(array($stmt, "bind_param"), $params);
The params passed to call_user_func_array, parameter 2, need to be referenced.
This is the working solution:
function makeValuesReferenced($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
$stmt = $conn->prepare($query);
//$stmt->bind_param($queryParamTypes, $queryParams);
call_user_func_array(array($stmt, 'bind_param'), makeValuesReferenced($queryParams));
$stmt->execute();
As per this answer
Not knowing about the reference thing mucked me around for a long time. Hope this helps some people.
I've made a function to query the database. This function takes an array, the id of the user I want to update
and a query operation.
if the query operation is UPDATE
if you look at the code below, would this be a good coding practice or is this bad code?
public function query($column, $search_value, $query_operation = "SELECT"){
if(strtoupper($query_operation == "UPDATE")){
$query = "UPDATE users SET ";
if(is_array($column)){
$counter = 1;
foreach($column as $key => $value){
if($counter < count($column)){
$query .= $key . ' = ?, ';
}else{
$query .= $key . ' = ? ';
}
$counter++;
}
$query .= "WHERE id = ?";
$stmt = $this->database->prepare($query);
$counter = 1;
foreach($column as $key => &$value){
$stmt->bindParam($counter, $value);
$counter++;
}
$stmt->bindParam($counter, $search_value);
if($stmt->execute()){
$stmt = $this->database->prepare("SELECT* FROM
users WHERE id = ?");
$stmt->bindParam(1, $search_value, PDO::PARAM_INT);
$stmt->execute();
return $this->build_array($stmt);
}
}
}
}
would love to hear some feedback.
I would NOT mix SELECT and UPDATE in the same function.
The following update function uses arrays for column names and values $columnNames & $values using unnamed parameters.
function update($tableName,$columnNames,$values,$fieldName,$fieldValue){
$sql = "UPDATE `$tableName` SET ";
foreach($columnNames as $field){
$sql .= $field ." = ?,";
}
$sql = substr($sql, 0, -1);//remove trailing ,
$sql .= " WHERE `$fieldName` = ?";
return $sql;
}
As table and column names cannot be passed as parameters in PDO I have demonstrated whitelistng of table names.
$tables = array("client", "Table1", "Table2");// Array of allowed table names.
Also array_push()to add value for last parameter (WHERE) into $values array
Use
if (in_array($tableName, $tables)) {
$sql = update($tableName,$columnNames,$values,$fieldName,$fieldValue);
array_push($values,$fieldValue);
$STH = $DBH->prepare($sql);
$STH->execute($values);
}
You can use similar technique for SELECT