php mysqli prepared statements select - php

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.

Related

Prepared Statement when not knowing how many parameters

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;
}

Building a dynamic PHP prepared statement from user entry

I have a database with student records, about 50 or so columns, all of which need to be searchable. I'm familiar with how to build a dynamic search query by appending fields. I can't seem to find any examples on searching a MySQL database using prepared statements that are dynamic in nature. For example, when searching you will only enter the fields you need to search by, so therefore, how do you build a dynamic prepared statement based on user input?
Here is an example of how to do it without prepared statements, however this is prone to SQL injection. I would need to "clean" each variable with a function. However, with a prepared statement this would be much more secure.
function clean($con,$var){
$var = trim($var);
$var = strtolower($var);
$var = mysqli_real_escape_string($con,$var);
return $var;
}
if(isset($_REQUEST['sub'])){
if(isset($_REQUEST['student_fname'])){
$student_fname = null;
if($_REQUEST['student_fname'] != ''){
$student_fname = '&& `student_fname` = \''.clean($con,$_REQUEST['student_fname']).'\' ';
}
}
if(isset($_REQUEST['student_lname'])){
$student_lname = null;
if($_REQUEST['student_lname'] != ''){
$student_lname = '&& `student_lname` = \''.clean($con,$_REQUEST['student_lname']).'\' ';
}
}
$sql = "SELECT * FROM `student_records`
WHERE 1=1
$student_fname
$student_lname
ORDER BY class ASC, student_lname ASC";
echo $sql;
}
As with many things, this would be a LOT easier with PDO. But, just off the top of my head this is how I would approach it. You need to build 3 structures here. The SQL, the list of parameter types ("s" or "i") and the list of parameters themselves. This is all pretty straightforward.
Getting this into the bind_param() function can be a bit tricky if you haven't done it before, but as detailed elsewhere the argument unpacking operator works nicely, once you have all your arguments into an array.
For your POST variables, remember that empty() checks if the array element is present, and also if it's non-empty. This will save you an extra check, with the only caveat being that empty("0") === true.
<?php
if (isset($_REQUEST['sub'])) {
$types = "";
$where = [];
$params = [];
if (!empty($_REQUEST['student_fname'])) {
$types .= 's';
$where[] = 'student_fname = ?';
$params[] = $_REQUEST['student_fname'];
}
if (!empty($_REQUEST['student_lname'])) {
$types .= 's';
$where[] = 'student_lname = ?';
$params[] = $_REQUEST['student_lname'];
}
if (count($where)) {
$where = "AND " . implode(" AND ", $where);
} else {
$where = "";
}
$sql = "SELECT * FROM student_records
WHERE 1=1
$where
ORDER BY class ASC, student_lname ASC";
$stmt = $con->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
while ($res = $stmt->get_result()) {
// ....
}
}
Note that this code allows someone to list the entire database; if that isn't your intention, wrap everything in the if (count($where)) statement.
In PDO, things are much easier, no binding needed and parameters are passed as an array already.
<?php
if(isset($_REQUEST['sub'])){
$where = [];
$params = [];
if(!empty($_REQUEST['student_fname'])) {
$where[] = 'student_fname = ?';
$params[] = $_REQUEST['student_fname'];
}
if(!empty($_REQUEST['student_lname'])){
$where[] = 'student_lname = ?';
$params[] = $_REQUEST['student_lname'];
}
if (count($where)) {
$where = "AND " . implode(" AND ", $where);
} else {
$where = "";
}
$sql = "SELECT * FROM student_records
WHERE 1=1
$where
ORDER BY class ASC, student_lname ASC";
$stmt = $con->prepare($sql);
$stmt->execute($params);
$data = $stmt->fetchAll(\PDO::FETCH_ASSOC);
}

Dynamic prepared statement, PHP

I've checked almost all questions that produce the same error but all of these questions bind parameters in some wrong way. Perhaps and most probably I too am binding params incorrectly though my case is different because I've dynamic query.
I am creating query dynamically from input which is being created perfectly. But problem comes from $stmt->bind_param statement within foreach loop. Here is my Code snippet that is erronous:
$query = "UPDATE users SET";
foreach($updationFields as $field => $value){
if($value != "-"){
$query = $query. " " . $field . " = :".$field.",";
}
}
$query = rtrim($query, ",");
$query = $query . " WHERE UserId = :UserId";
$stmt = $this->conn->prepare($query);
foreach($updationFields as $field => $value){
echo $field;
if($value != "-"){
$input = ":".$field;
$stmt->bind_param($input, $value); // This line produces error
}
}
$stmt->bind_param(":UserId", $userId);
$stmt->execute();
Here is produced dynamic "string query" for one field:
UPDATE users SET fullName = :fullName WHERE UserId = :UserId
Error says: Fatal error: Call to a member function bind_param() on a non-object in
Any Idea what i am doing wrong?
As pointed out by #Fred-ii- and #commorrissey :Placeholder is supported by PDO not mysqli so so I had to:
Replace :Placeholders with ?
Call bind_param with call_user_func_array feeding dynamic references as expected by mysqli_stmt.
Here is the code that creates dynamic binding:
$params = array();//
$params[] = $type;
$i=0;
foreach($updationFields as $field => $value){
if($value != "-"){
$bind_name = 'bind' . $i;
$$bind_name = $value;
$params[] = &$$bind_name;
$i++;
}
}
$bind_name = 'bind' . $i;
$$bind_name = $userId;
$params[] = &$$bind_name;
$return = call_user_func_array(array($stmt,'bind_param'), $params);

(PHP) Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement

I have the following query.
$mysqldb = mysqlidb_class();
$query = "select * from post where idx < ?"
Then I bind the parameter and execute.
$bindvariable = array();
array_push($bindvariable, $post_photoidx);
array_push($bindvariable, $post_idx);
$res = $mysqldb->rawQuery($query, $bindvariable);
Then I get the following error.
Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement
But when I change the query like below, the error disappears.
$query = "select * from post where idx = ?"
What am I doing wrong here?
Here is the class I use for the mysql query
<?php
class MysqliDb
{
......
public function rawQuery ($query, $bindParams = null, $sanitize = true)
{
$this->_query = $query;
if ($sanitize)
$this->_query = filter_var ($query, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES);
$stmt = $this->_prepareQuery();
if (is_array($bindParams) === true) {
$params = array(''); // Create the empty 0 index
foreach ($bindParams as $prop => $val) {
$params[0] .= $this->_determineType($val);
array_push($params, $bindParams[$prop]);
}
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($params));
}
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return $this->_dynamicBindResults($stmt);
}
......
protected function _buildQuery($numRows = null, $tableData = null)
{
$this->_buildJoin();
$this->_buildTableData ($tableData);
$this->_buildWhere();
$this->_buildGroupBy();
$this->_buildOrderBy();
$this->_buildLimit ($numRows);
$this->_lastQuery = $this->replacePlaceHolders ($this->_query, $this->_bindParams);
if ($this->isSubQuery)
return;
// Prepare query
$stmt = $this->_prepareQuery();
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
return $stmt;
}
protected function _prepareQuery()
{
if (!$stmt = $this->_mysqli->prepare($this->_query)) {
trigger_error("Problem preparing query ($this->_query) " . $this->_mysqli->error, E_USER_ERROR);
}
return $stmt;
}
protected function refValues($arr)
{
//Reference is required for PHP 5.3+
if (strnatcmp(phpversion(), '5.3') >= 0) {
$refs = array();
foreach ($arr as $key => $value) {
$refs[$key] = & $arr[$key];
}
return $refs;
}
return $arr;
}
......
} // END class
You mightn't use array(2).
Instead, use
$sql = "select * from post where idx < :i";
$stmt->bindparam("i", 2);
$stmt->execute();
or use
$array = array($something,$else,$whatever);
$sql = "select * from post where idx < ?";
$stmt->bindparam("i", $array[2]);
$stmt->execute();
It looks like you are not preparing your query before biding parameters to it.
$sql = "SELECT * FROM post WHERE idx < ?";
if($stmt = $stmt->prepare($sql)) {
$stmt->bind_param('i', 2);
$stmt->execute();
}
Santize filters ruined my SQL query.
I have changed the source to the following to resolve the problem.
$mysqldb->rawQuery($query, $bindvariable, false);

building db query with a for loop

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

Categories