I have been old school using mysql_query and starting out now using PDO. Which is great!
But in my old scripts I had build a dynamic query builder, and i'm having a tough time porting that over using PDO.
If anyone can give me some direction that would be great!
Here is the theory of it.
I have an array of
the DB Fields and Values (upon insert).
Create the query string to product a valid PDO transaction
Here is a portion of what i'm trying to do.
public $dbFields; // This is an array of the fields plus VALUES
public function select($where, $limit) {
// This is what I **had** before
$query = "SELECT ". implode(", ", $this->dbFields) ." FROM ". $this->table." WHERE ". $where ." ". $limit."";
// Now i need to convert that to PDO
$this->connection->beginTransaction();
# START Query
$select = $this->connection->prepare("SELECT {$this->fieldNames} FROM {$this->table}");
// I need to BIND my params and values, but i'm not sure the best route to take when I have a WHERE clause that includes, "AND" / "OR" operators.
# EXECUTE the query
$select->execute();
$this->connection->commit();
}
This is what I HAD before
$results = $db->select("userId = 111 OR userId = 222");
But what i'm thinking I need to do is use something more like
$results = $db->select(array("userId"=>111, "userId"=>222));
I know this is a tall order, and I hope it makes sense in what i'm trying to do, but any help in trying to build these queries would be greatly appreciated.
You'll need a separate $params parameter to your select method. I took the liberty of providing defaults for the method parameters. Like #userXxxx notes, you don't need a transaction just to do a SELECT.
<?php
class db {
public $connection; //PDO
public $dbFields; // This is an array of the fields plus VALUES
public function select($where = '1', $params = array(), $limit = '', $fetchStyle = PDO::FETCH_ASSOC) { //fetchArgs, etc
$fields = implode(', ', $this->dbFields);
//create query
$query = "SELECT $fields FROM {$this->table} WHERE $where $limit";
//prepare statement
$stmt = $this->connection->query($query);
$stmt->execute($params);
return $stmt->fetchAll($fetchStyle);
}
//...
}
$where = 'userId IN(:userId1, :userId2)';
$params = array(':userId1' => 111, ':userId2' => 2222);
$db->select($where, $params);
Notes:
If you really want, you can add additional method parameters to match up with all the flexibility of PDOStatement::fetchAll.
I'm not sure what you mean about $dbFields being "fields plus VALUES". Can you explain?
[Edit]
You might want to take a look at the docs/examples for PDOStatement::execute, since that seemed to be where your confusion was rooted--in particular, the $input_parameters method parameter.
What about this?
public function select($where, $limit) {
$query = "SELECT ". implode(", ", $this->dbFields) ." FROM ". $this->table." WHERE ". $where ." ". $limit."";
$this->connection->query($query);
}
//Use what you had before:
$results = $db->select("userId = 111 OR userId = 222");
Not sure why you want to use transaction (for all-or-nothing basis or catching exceptions and rollback) or prepared queries (for sending multiple queries)...
Related
I am trying to migrate to Mysqli and I got my Mysql code to search for parameters like this:
$querySt = "SELECT userID FROM myTable";
if (isset($_POST["UserID"])) {
if (ctype_digit($_POST["UserID"])) {
addWhereIfNoHave();
$in_userID = mysql_real_escape_string($_POST["UserID"]);
$querySt .= " UserID = '$in_userID'";
}
}
if (isset($_POST["name"])) {
addWhereIfNoHave();
$in_name = mysql_real_escape_string($_POST["name"]);
$querySt .= " imgName LIKE LOWER('%$in_name%')";
}
if (isset($_POST["ScoreLessThan"])) {
if (ctype_digit($_POST["ScoreLessThan"])) {
addWhereIfNoHave();
$in_ScoreLessThan = mysql_real_escape_string($_POST["ScoreLessThan"]);
$querySt .= " Score < '$in_ScoreLessThan'";
}
}
...
...
there are other if statements here looking for other post data, and
they keep on adding parameters into mysql query string just like above.
...
...
//this function is called in those if statements above. It either adds "WHERE" or "AND".
function addWhereIfNoHave(){
global $querySt;
if (strpos($querySt, 'WHERE') !== false){
$querySt .= " OR";
return true;
}else{
$querySt .= " WHERE";
return false;
}
}
This function works ok looking for all the parameters input from PHP post. However, I am migrating this to Mysqli, and I have a bit of trouble converting this code to Mysqli version. For example,
$stmt = $conn->prepare("SELECT userID FROM myTable WHERE UserID = ? AND name= ?");
$stmt->bind_param('ss', $userid, $name);
Suppose, I wanna search the table using 2 variables, I bind 2 variables like above, but in the case of my Mysql above, I keep on extending additional parameters into the string before executing the mysql query.
But for Mysqli, how can we do this? Is it possible to bind additional parameters and extending the string for prepare statement like Mysql code above? How should this problem be approach for Mysqli?
My current problem is mainly with the bind_param. I could concatenate the search query further and add all the '?' into the prepare statement, but with different variable types and number variables needed to be specified in bind_param, this is where I am stuck.
I am trying to find a way to create a function in PHP that will wrap a SQL query given in the parameter so that I can prevent SQL Injection in the function that can then be called many times throughout my application. Rather than repeating the same statements for each and every query.
For example say I have the following PHP code that prepares and executes a query to prevent SQL injection:
$name = "$_POST['name']";
$stmt = $db->prepare('SELECT * FROM test_table WHERE test_name = ?');
$stmt->execute(array($name));
For each query my application will need to make these statements will need to be repeated. I want a way to prevent having to do this each time, rather I would simply want to call a function each time and pass in the query.
How would I wrap this in a function that can then be called whenever I need to make a query in my application, given that I do not know in advance the amount of parameters that would need to be parameterized. The above query has one parameterized query, but each query may have a different amount.
Note:
I am using PDO statements
Something like this:
public function query($query)
{
// statements here
}
Where the query is passed in as a parameter.
Does anyone know how I can achieve this?
Currently, I am using something like this that might work for you.
Example:
function superQuery($query, $params, $type = null) {
$pdo = new pdo(...);
$stmt = $pdo->prepare($query);
$stmt->execute($params);
if ($type === "select") {
$result = $stmt->fetchAll();
return $result;
} else {
return $stmt;
}
$query = "SELECT row FROM column WHERE row1 = ? AND row2 = ?";
$params = [$row1, $row2];
$type = "select";
$row = selectQuery($query, $params, $type);
// returns multidimensional array or true/false depending if argument is used //
There's lots of ways you can do it. You could also pass a count argument if you wanted to return a count instead of a result set. But hopefully this points you in the right direction and gives you some ideas.
How do I change this function to another function. I don't want to use the get_result
I searched online but could not find an answer that could help me.
public function Select($Table_Name, $Conditions='' ,$Array_Conditions_Limit=NULL , $OrderBy='', $Limit='', $Selected_Fields='*')
{
$Query = "SELECT ".$Selected_Fields." FROM ".$Table_Name;
if(!empty($Conditions))
$Query .= " WHERE ".$Conditions;
if(!empty($OrderBy))
$Query .= " ORDER BY ".$OrderBy;
if(!empty($Limit))
$Query .= " LIMIT ".$Limit;
$Statment = $this->ConnectionResult->prepare($Query);
if(isset($Array_Conditions_Limit) )
{
$Statment = $this->DynamicBindVariables($Statment, $Array_Conditions_Limit);
$Statment->execute();
return $Statment->get_result();
}
else
$Statment->execute();
return $Statment->get_result();
}
This also functions dynamic bind variables
private function DynamicBindVariables($Statment, $Params)
{
if (is_array($Params) && $Params != NULL)
{
// Generate the Type String (eg: 'issisd')
$Types = '';
foreach($Params as $Param)
{
$Types .= $this->GetType($Param);
}
// Add the Type String as the first Parameter
$Bind_names[] = $Types;
// Loop thru the given Parameters
for ($i=0; $i<count($Params);$i++)
{
$Bind_name = 'bind' . $i;
// Add the Parameter to the variable
$$Bind_name = $Params[$i];
// Associate the Variable as an Element in the Array
$Bind_names[] = &$$Bind_name;
}
// Call the Function bind_param with dynamic Parameters
call_user_func_array(array($Statment,'bind_param'), $Bind_names);
}
elseif(isset($Params) && !empty($Params))
{
$Types = '';
$Types .= $this->GetType($Params);
$Statment->bind_param($Types ,$Params);
}
return $Statment;
}
I using the return value as follows:
$myresult =Select('post','post_category=?' ,2 );
$row = $myresul2->fetch_object()
First of all, I find this approach utterly useless. What are you actually doing is dismembering fine SQL sentence into some anonymous parts.
"SELECT * FROM post WHERE post_category=?"
looks WAY better than your anonymous parameters of which noone have an idea.
'post','post_category=?'
One can tell at glance what does first statement to do. and have no idea on the second. Not to mention it's extreme:
'post','post_category=?',NULL, NULL, 'username, password'
So, instead of this kindergarten query builder I would rather suggest a function that accepts only two parameters - a query itself and array with bound data:
$myresult = Select("SELECT * FROM post WHERE post_category=?", [2]);
To make it more useful, I wouild make separate functions to get different result types, making your second line with fetch_object() obsolete (however, speaking of objects, they are totally useless to represent a table row). Example:
$row = $db->selectRow("SELECT * FROM post WHERE post_category=?", [2]);
Look: it's concise yet readable!
As a further step you may wish to implement more placeholder types, to allow fields for ORDER BY clause be parameterized as well:
$data = $db->getAll('id','SELECT * FROM t WHERE id IN (?a) ORDER BY ?n', [1,2],'f');
you can see how it works, as well as other functions and use cases in my safeMysql library
this is my insertion method in PDO, it is working 100%. this 'insert' method accepts table, column and value but i want to make it versatile. (i want to insert values with or without the column names)
public function insert($table, $pair = array()){
try{
$Sql = "INSERT INTO $table ( ";
$Sql .= implode(", ", array_keys($pair));
$Sql .= " )";
$Sql .= " VALUES (";
$Sql .= implode(", ", array_fill("0", count($pair), " ?"));
$Sql .= " )";
$array = array_combine(array_keys(array_fill("1", count($pair), ":")), $pair);
$ready = $this->conn->prepare($Sql);
foreach($array as $key => $value)
{
$ready->bindValue($key, $value, PDO::PARAM_STR);
}
$ready->execute();
}
catch(Exception $e){
$this->trace .= " • ". $e->getMessage();
}
}
$new = new community();
echo $new->insert("table", array("Col1" => "value1", "col1" => "value1"));
There are two problems with your function.
It is vulnerable to SQL injection.
It is not flexible. Following the pattern, you are going to have a thousand functions of this kind, which will make your code into mess. Yet it would be always limited subset against real SQL.
What you really need is a function that can create a SET statement out of array and a list of allowed fields.
As a further improvement you may devise a custom placeholder for this statement.
Having these two things you can work out a single general purpose function to run all the DML queries like this:
$db->query("INSERT INTO t SET %u", array("Col1" => "value1", "col1" => "value1"));
It will cost you 3 additional words (insert, into and set), but it will be
readable. Everyone can understand SQL. While to read your function one need a documentation
flexible. It can support any queries and modifiers, not only one single-formed insert.
Every query you wish you can run with this single function:
$data = array("Col1" => "value1", "col1" => "value1");
$db->query("INSERT IGNORE INTO t SET %u", $data);
$db->query("REPLACE INTO t SET %u", $data);
$db->query("DELETE FROM t WHERE id = ?", $id);
// and so on
No dedicated functions actually needed.
Also, you have to always verify a set of fields against a hardcoded white list, to let a user insert only fields they are allowed to. Do not let a user to alter privileges, messages count and so on.
But even without custom placeholder it would require no set of SQL-mapped functions but just a function to create a SET and a general purpose query execution function:
$allowed = array("name","surname","email"); // allowed fields
$sql = "INSERT INTO users SET ".pdoSet($fields,$values);
$stm = $dbh->query($sql ,$values);
I have a web application with lots of data, and a search/filter function with several fields, such as name, status, date, and so on. I have been using parameterized queries like this for the regular (non-search) queries:
$id = $_POST['itemID'];
$db = mysqli_connect($host, $username, $password, $database);
$sql_query = "SELECT * FROM tbl_data WHERE ID = ?";
$stmt_query = mysqli_prepare($db, $sql_query);
mysqli_stmt_bind_params($stmt_query, "i", $id);
mysqli_stmt_execute($stmt_query);
//and so on..
How would I protect agains SQL-injection with multiple, optional parameters? There can be up to 10 separate parameters that might or might not be set.
Edit as the question seems unclear:
My example was with one parameter, which is not optional. I know this protects against sql-injection. How would I go about doing this with 10 parameters, where one or several could be set at the same time? E.g. a query such as this:
SELECT * FROM tbl_data
WHERE NAME = ?
AND STATUS = ?
AND DATE = ?
AND PARAM4 = ?
AND PARAM5 = ?
where the user only wants to search on name and date. How would I do the binding? It's not a good idea to check for each of the 100 possible combinations of search terms.
You are already protected against sql injection, as you are using the mysqli_stmt_bind_params which will escape properly for you.
Edit.
You could switch to some database framework to have a clean and beautiful code.
Otherwise... this is so old spaghetti style... but I love it :D
It's quite easy to expand your code to work with an unknown number of parameters. You should just loop on your parameters and at the same time 1. build your query string with the question mark notation, and add your parameters to an array, which you will be passing to maxdb_stmt_bind_param ( resource $stmt , string $types , array &$var ).
So it would look like this. It assumes at least ONE parameter is there (but it's trivial to avoid this).
$sql = "SELECT * FROM tbl_data WHERE ";
$and = '';
$types = '';
$parameters = array();
foreach($_POST as $k => $v) {
// check that $k is on your whitelist, if not, skip to the next item
$sql .= "$and $k = ?";
$and = " AND ";
$parameters[] = $v;
$types .= 's';
}
$stmt_query = mysqli_prepare($db, $sql);
mysqli_stmt_bind_params($stmt_query, $types, $parameters);
I recommend switching to PDO. It's built into PHP like the mysqli extension, but has a cleaner syntax and allows you to pass in your parameter values as an array, which you can easily construct dynamically with as many elements as needed.