The Problem
So I'm writing my web based application and it dawns on me "Durr, your stuff is wide open to SQL injection and whatnot! Rewrite db class!"
I'm currently re-writing my $db class and I am having a significant amount of trouble understanding how I'm supposed to implement prepared statements.
Previously...
I used to use something like this:
$db->runQuery("SELECT * FROM someTable WHERE someField = '$var1'");
while ($result = mysql_fetch_array($db->result){
// ... ugh, tedious
}
Invariably, when performing select statements, I'm grabbing an array, and looping through the results.
I understand that...
I should be burnt at the stake for using non-prepared statements in MySQL.
I have to let mysql know what type of parameter each variable is. (Or do I)?
I'd like to...
Be able to pass my query, and values to my new function (let's use select as an example) which would then return a result for me to work with (as an assoc. array of values);
$query = "SELECT * FROM someTable WHERE someField = ? AND anotherField = ?";
$params = array($var1, $var2);
$result = $db->doSelect($query, $params);
// Then do all sorts of neat stuff with $result - huzzah!
I'm having trouble with...
Understanding how I would bring all the information together.
How do I present an array of values and have that mushed together with my prepared statement?
With said mushed statement, how do I run it (execute()?) and have it return an array?
I'm sorry if my question is somewhat roundabout, however I'm frazzled from trying to understand it. If more information is required, please let me know and I'll add it.
Here is what I've written for a Prepare/Execute function set. These are of course part of a larger DB object.
/**
* Prepares a query to be run, storing the data in $this->preparedTokens
* Use the following characters to indicate how the data is to be put into SQL statement
* ? -> escaped and quoted (with single quotes) before inserting
* ^ -> inserted as is
* & -> implodes the array escpaping each value
* # -> implodes the array (no escaping)
*
* #param string $sql The SQL statement to prepare
*
* #return int The key of prepare sql query to be passed to $this->Execute()
*/
public function Prepare($sql) {
$tokens = preg_split('/((?<!\\\)[#&?^])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE);
// loop through removing any escaped values
foreach ($tokens as $key => $val) {
switch ($val) {
case '?' :
case '&' :
case '#' :
break;
default :
$tokens[$key] = preg_replace('/\\\([#&?^])/', "\\1", $val);
break;
} // switch
} // foreach
$this->preparedTokens[] = $tokens;
end($this->preparedTokens);
return key($this->preparedTokens);
} // function Prepare
/**
* Creates the SQL placing the data in the appropriate places and then runs the sql
*
* #param int $preparedKey The key of the prepared sql
* #param array $data The array of data to put into the query (the count of this array must match that of the prepared query)
*
* #return object false if the $preparedKey does not exist in $this->preparedTokens
* false if count of needed values in sql statement does not equal the number of keys in the data array
* otherwise, the result of $this->Query()
*/
public function Execute($preparedKey, $data) {
if (isset($this->preparedTokens[$preparedKey])) {
$tokens = $this->preparedTokens[$preparedKey];
$query = '';
$dataKey = 0;
$count = 0;
// count the number of tokens we have
$validTokens = array('?', '^', '&', '#');
foreach ($tokens as $val) {
if (in_array($val, $validTokens)) {
++$count;
} // if
} // foreach
// check to ensure we have the same number of tokens as data keys
if ($count != count($data)) {
trigger_error('Query Error: The number of values received in execute does not equal the number of values needed for the query', E_USER_ERROR);
return false;
} // if
// loop through the tokens creating the sql statement
foreach ($tokens as $val) {
switch ($val) {
case '?' :
$query .= "'" . $this->EscapeString($data[$dataKey++]) . "'";
break;
case '^' :
$query .= $data[$dataKey++];
break;
case '&' :
$query .= $this->ImplodeEscape($data[$dataKey++]);
break;
case '#' :
$query .= implode(',', $data[$dataKey++]);
break;
default :
$query .= $val;
break;
} // switch
} // foreach
return $this->Query($query);
} else {
return false;
} // if
} // function Execute
/**
* Runs $this->Prepare() then $this->Execute() for the sql and the data
* Use the following characters to indicate how the data is to be put into SQL statement
* ? -> escaped and quoted (with single quotes) before inserting
* ^ -> inserted as is
* & -> implodes the array escpaping each value
* # -> implodes the array (no escaping)
*
* #param string $sql The SQL statement to prepare
* #param array $data The array of data to put into the query (the count of this array must match that of the prepared query)
*
* #return object returns value from $this->Query() if Execute was successful
* otherwise it'll be false
*/
public function PrepareExecute($sql, $data) {
return $this->Execute($this->Prepare($sql), $data);
} // function PrepareExecute
$this->Query() executes the MySQL statement and then returns different values depending on what the statement is (based on the first 6 characters of the statement, trimmed):
false if failed (use $this->GetError() to get error message)
if successful INSERT, then the insert id
if successful DELETE or UPDATE or REPLACE, then the number of affected rows
if successful SELECT or any other query type, then the Query object
I'm not sure if this is what you are looking for, but it might help.
Forgot to mention this, but most of the ideas came from the Pear::DB class: http://pear.php.net/package/DB
See if you can make use of following.
Let me know if you need a more detailed implementation
call_user_func_array(array($stmt,"bind_result"), $params);
Related
simple thing: my code is just not working. Neither INSERT nor SELECT is working in my PDO. Probably I have something wrong, but I'm not a code master, so I need your help.
if (isset($_POST['submit']))
{
try
{
$connection = new PDO('sqlite:../tracker.db');
$name = $_POST['name'];
$unitsize = $_POST['unitsize'];
$line = $_POST['line'];
$mmr = $_POST['mmr'];
$lifespan = $_POST['lifespan'];
$connection->exec("INSERT INTO unit (name, unitsize, line, mmr, lifespan)
VALUES ('$name', '$unitsize', '$line', '$mmr', '$lifespan')");
$new_unit = "SELECT unit_id
FROM unit
ORDER BY unit_id DESC
LIMIT 1";
foreach ($connection->query($new_unit) as $row) {
$id = $row['unit_id'];
};
}
catch(PDOException $error)
{
echo $error->getMessage();
}
}
Of course I'm aware that SELECT without records can't work... But my begginer's intuition says, that it could also hava a mistake.
PS: I know, that the code may be a bit messy... sorry for your eyes bleeding :(
EDIT:
WHAT I WANT TO ACHIEVE
There is a database tracker.db with existing tables (confirmed by SQLite Browser)
I want to INSERT some data from my form.
After inserting I want to get the last unit_id registered in my DB into variable $id(unit_id is AUTOINCREMENT and PRIMARY KEY)
That's all
Well, from looking into your code, I could say that the error (at least one) lie in the following parts:
Connection creation.
SQL statement - the apostrophs (').
Uncatched, failure signalizing, returned values of PDO::exec() or/and PDO::query(). You are using both functions in a proper way regarding their definitions, but maybe they returned FALSE on failure. Situation which you didn't handled at all and which is stated in the "Returned Values" parts of their corresponding docus on php.net.
So, because the problem with your code was that you didn't know why it didn't work, e.g. you didn't received any error or sign of it, I thought to show you a complete way to use error reporting + prepared statements + validations + exception handling. Please note that all these four elements are mandatory if you want to code a secure and solid PDO solution. More of it, when you are applying them in a proper manner, you'll always know where a problem (or more) lies. And you'll have a lot more efficiency in code writing, because you'll not loose any more time (sometimes hours!) for finding errors.
Also, how you structure your code is up to you. I presented you here a procedural form, in which you can follow the steps with ease. A better form would be one implemented in an object oriented way.
Recommendations:
Always prepare the sql statements (read this) in order to avoid bad intended db injections. In your case, here, this implies that you must use PDO::prepare() + PDOStatement::execute() instead of PDO::exec (read the "Description" from PDO::exec on php.net).
PDO is a very powerfull data access abstraction system. But, in order to use it the right way, you need to always read the documentation for each of its function which you are using, and ESPECIALLY the "Return Values" part. This would be a "must", because there are cases when, on failure, a value can be returned in form of a bool FALSE OR an exception can be thrown. These cases must then be properly handled. For example, in case of PDO::prepare():
If the database server cannot successfully prepare the statement,
PDO::prepare() returns FALSE or emits PDOException (depending on error
handling).
Feel free to ask anything if you have unclarities.
Good luck.
<?php
/*
* Try to include files using statements
* only on the top of the page.
*/
require "../config.php";
require "../common.php";
/*
* Set error reporting level and display errors on screen.
*
* =============================================================
* Put these two lines in a file to be included when you need to
* activate error reporting, e.g the display of potential errors
* on screen.
* =============================================================
* Use it ONLY ON A DEVELOPMENT SYSTEM, NEVER ON PRODUCTION !!!
* If you activate it on a live system, then the users will see
* all the errors of your system. And you don't want this !!!
* =============================================================
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
/*
* ===================================================
* Two functions used for automatically binding of the
* input parameters. They are of course not mandatory,
* e.g. you can also bind your input parameters one
* by one without using these functions. But then
* you'd have to validate the binding of each input
* parameter one by one as well.
*
* Put these two functions in a file to be included,
* if you wish.
* ===================================================
*/
/**
* Get the name of an input parameter by its key in the bindings array.
*
* #param int|string $key The key of the input parameter in the bindings array.
* #return int|string The name of the input parameter.
*/
function getInputParameterName($key) {
return is_int($key) ? ($key + 1) : (':' . ltrim($key, ':'));
}
/**
* Get the PDO::PARAM_* constant, e.g the data type of an input parameter, by its value.
*
* #param mixed $value Value of the input parameter.
* #return int The PDO::PARAM_* constant.
*/
function getInputParameterDataType($value) {
if (is_int($value)) {
$dataType = PDO::PARAM_INT;
} elseif (is_bool($value)) {
$dataType = PDO::PARAM_BOOL;
} else {
$dataType = PDO::PARAM_STR;
}
return $dataType;
}
/*
* ======================
* Hier begins your code.
* ======================
*/
try {
// Read from HTTP POST.
$name = $_POST['name'];
$unitsize = $_POST['unitsize'];
$line = $_POST['line'];
$mmr = $_POST['mmr'];
$lifespan = $_POST['lifespan'];
// Create a PDO instance as db connection to sqlite.
$connection = new PDO('sqlite:../tracker.db');
// The sql statement - it will be prepared.
$sql = 'INSERT INTO unit (
name,
unitsize,
line,
mmr,
lifespan
) VALUES (
:name,
:unitsize,
:line,
:mmr,
:lifespan
)';
// The input parameters list for the prepared sql statement.
$bindings = array(
':name' => $name,
':unitsize' => $unitsize,
':line' => $line,
':mmr' => $mmr,
':lifespan' => $lifespan,
);
// Prepare the sql statement.
$statement = $connection->prepare($sql);
// Validate the preparing of the sql statement.
if (!$statement) {
throw new UnexpectedValueException('The sql statement could not be prepared!');
}
/*
* Bind the input parameters to the prepared statement
* and validate the binding of the input parameters.
*
* =================================================================
* This part calls the two small functions from the top of the page:
* - getInputParameterName()
* - getInputParameterDataType()
* =================================================================
*/
foreach ($bindings as $key => $value) {
// Read the name of the input parameter.
$inputParameterName = getInputParameterName($key);
// Read the data type of the input parameter.
$inputParameterDataType = getInputParameterDataType($value);
// Bind the input parameter to the prepared statement.
$bound = $statement->bindValue($inputParameterName, $value, $inputParameterDataType);
// Validate the binding.
if (!$bound) {
throw new UnexpectedValueException('An input parameter could not be bound!');
}
}
// Execute the prepared statement.
$executed = $statement->execute();
// Validate the prepared statement execution.
if (!$executed) {
throw new UnexpectedValueException('The prepared statement could not be executed!');
}
/*
* Get the id of the last inserted row.
* You don't need to call a SELECT statement for it.
*/
$lastInsertId = $connection->lastInsertId();
/*
* Display results. Use it like this, instead of a simple "echo".
* In this form you can also print result arrays in an elegant
* manner (like a fetched records list).
*
* Theoretically, this statement, e.g. the presentation of results
* on screen, should happen outside this try-catch block (maybe in
* a HTML part). That way you achieve a relative "separation of
* concerns": separation of the fetching of results from the
* presentation of them on screen.
*/
echo '<pre>' . print_r($lastInsertId, TRUE) . '</pre>';
// Close the db connecion.
$connection = NULL;
} catch (PDOException $exc) {
echo '<pre>' . print_r($exc, TRUE) . '</pre>';
// echo $exc->getMessage();
// $logger->log($exc);
exit();
} catch (Exception $exc) {
echo '<pre>' . print_r($exc, TRUE) . '</pre>';
// echo $exc->getMessage();
// $logger->log($exc);
exit();
}
?>
I have a similar problem like the author of this question:
MySQLI binding params using call_user_func_array
But before my question get duplicated tag, the solution didn't worked out for me.
That actually is my code:
// in classfoodao.php
function updateClassfoo(Classfoo $classfoo){
$values = array();
global $conn,$f;
$pattern = "";
/* updateFields get all defined attributes in classfoo object and then write in the $sql string
* A prepared statement only for the not null attribs,
* and also put the attribs in the same order in the $values array.
* The same are done for the $pattern string.
* $values and $pattern are passed by reference.
*/
$sql = $classfoo->updateFields($values, $pattern);
if (!empty($values) && !empty($pattern)) {
$stmt = $conn->prepare($sql);
$temp = array($pattern);
for ($i = 0, $count = count($values); $i < $count; $i++) {
$addr = &$values[$i];
array_push($temp, $addr);
}
call_user_func_array(array($stmt, "bind_param"), $temp);
} else {
return true;
}
}
And I still getting this Warning:
PHP Warning: Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
Following with the error:
Execute failed on update: (2031) No data supplied for parameters in prepared statement
I'm not using any framework for PHP, how can I create an Array of references to solve this problem?
I still get the feeling we do not have the whole picture here, however I think there is enough to formulate a sufficient answer.
It sounds like you want a function that takes a mysqli_stmt string and an array as it's 3 parameters and returns a parameterized SQL statement. In code your method signature would look like
/**
* Builds a parameterized Sql query from the provided statement, data type pattern and params array.
*
* #param string $stmt The query string to build a parameterized statement from
* #param string $pattern The data type pattern for the parameters i.e. sssd for 3 strings and an interger.
* #param array $params A sequential array of the parameters to bind to the statement.
*
* #return mysqli_stmt The parameter bound sql statement ready to be executed.
*/
public function bind(string $stmt, string $pattern, array $params) : mysqli_stmt
You would call such a method as such
$stmt = bind(
'INSERT INTO myTable (str_field, str_field_2, int_field) VALUES (?, ?, ?)',
'ssd',
['val1', 'val2', 1337]
);
$result = $stmt->execute();
The implementation should be as trivial as this
public function bind(string $stmt, string $pattern, array $params) : mysqli_stmt {
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');
$preparedStmt = $mysqli->prepare($stmt);
$preparedStmt->bind_param($pattern, ...$params);
return $preparedStmt;
}
I of course would suggest you use dependency injection rather than creating a new mysqli object every time the function is called and you should do some error checking in regards to the $pattern and $param counts. There's still a lot I can't see but I hope this gets you on your way.
This question already has answers here:
Insert query on page load, inserts twice?
(3 answers)
Closed 2 years ago.
I usually don't post detailed versions of my code; however, in this case it may be necessary to figure out the problem. I have a class method that I cannot stop from executing twice. Is there any particular information I am missing on MySQLi prepared statements? I have read similar questions that asks the same to no avail.
I previously asked a question about using eval for dynamic queries in prepared statements as far as it's convention and best practices. I was told to use call_user_func_array() and it worked perfectly; however, I failed to notice that the statement executed twice each time, even with the old eval() code. So I put together a snippet of my ACTUAL code which should pretty much explain itself through my comments
function insert($table, $query)
{
/**
*
* This code assumes you have a MySQLi connection stored in variable $db
* USAGE: insert(table, array('field' => 'value');
*
**/
// Sets the beginning of the strings for the prepared statements
$fields = $values = "(";
$types = "";
$params = array();
foreach($query as $key => $val)
{
// array keys = fields, and array values = values;
$fields.= $key;
// concatenate the question marks for statement
$values.= "?";
// concatenate the type chars
$types.= is_string($val) ? "s" : (is_int($val) ? "i" : (is_double($val) ? "d" : "b"));
// pass variables to array params by reference for call_user_func_array();
$params[] = &$query[$key];
if($val == end($query))
{
$fields .= ")";
$values .= ")";
array_unshift($params, $types);
}
else
{
$fields .= ", ";
$values .= ", ";
}
}
$str = "INSERT INTO {$table} {$fields} VALUES {$values}";
if($stmt = $db->prepare($str))
{
call_user_func_array(array($stmt, 'bind_param'), $params);
/**
*
* This is where I am pulling my hair out of my head and being 3
* nothces away from banging my own head into the screen and
* being without a computer at all.
*
* I have tried everything I can think of. I gotta be missing
* something
*
* IT JUST KEEPS SENDING 2 ROWS DANG IT!
*
**/
/////////////////////
$stmt->execute();//// <---Help is needed here
/////////////////////
//-- Close connection;
$stmt->close();
}
else
{
//-- Send a nice readable error msg
die("<center><h3>FAULTY QUERY STRING</h3><h4>Please check query string</h4><p>{$str}</p>");
}
}
Changed code format from OOP to regular function for testing without having to create a class.
old question, but people might still stumble upon it, I did.
I think the mysqli_query function is mainly for retrieving data, I had the same problem, and fixed it by using mysqli_real_query on insert and update queries.
I'm trying to figure out how to convert my history script from mysql_query() to PDO. I have a form with 4 input fields which you can randomly select. Which means there can be 0, 1, 2, 3, 4 fields selected depending on which info you're trying to get.
I've tried to query db like this:
$q = $db->prepare('SELECT date,
name,
action
FROM history
WHERE name = :name
AND action = :action');
$q->bindParam(':name', $Name, PDO::PARAM_STR, 20);
$q->bindParam(':action', $Action, $PDO::PARAM_STR, 20);
$q->execute();
But this doesn't work if I don't have any fields selected and want the whole history shown.
With mysql_query() I'd just do this:
mysql_query('SELECT date,
name,
action
FROM history
$Name
$Action');
Which means if there's no $Name or $Action they're simply not included in the query.
Should I just copy/paste the old query into $q = $db-query('')? But that kind of defeats the purpose of using PDO.
You could always assign default values to the params which conform to the column names.
That way your query will in the default case end up as where column = column and when there is a value present it will be where column = value.
EDIT:
Of course, my logic was slightly flawed, since bindParam does not work that way. Instead, you should incrementally build your statement according to the params set.
/* Start with the most general case for the sql query.
* The where part always evaluates to true and will thus
* always return all rows and exists only to make appending
* further conditions easier.
*/
$q = 'SELECT date, name, action FROM history WHERE 1';
/* Prepare a params array in any way you wish. A loop might be more
* efficient if it is possible, but since in this example you have
* only 2 variables, it didn't seem necessary
*/
$params = array();
if (! empty($Name)) {
$params['name'] = $Name;
}
if (! empty($Action)) {
$params['action'] = $Action;
}
/* When the params array is populated, complete the sql statement by
* appending the param names joined with ANDs
*/
foreach ($params as $key => $value) {
$q .= sprintf(' AND `%s` = :%s', $key, $key);
}
/* When the query is complete, we can prepare it */
$stmt = $db->prepare($q);
/* Then bind the values to the prepared statement
*/
foreach ($params as $key => $value) {
// Using bindValue because bindParam binds a reference, which is
// only evaluated at the point of execute
$stmt->bindValue(':'.$key, $value);
}
/* Now we're ready to execute */
$stmt->execute();
In this example, the empty check could've been done in the loop where we complete the sql statement, but that would've given you a less general example.
This example also leaves out the type param to bindValue, but that would be easily implemented, e.g. by changing the array value to an object or array having the type as a member, or by duck typing inside the assigning loop.
The query building could in this form easily be put in a function that would work for all your database querying needs, as long as you provide it the initial (general case) query along with the params array.
I was told to use bind parameters so that I could insert text into my db that had quotes in it. But, I am pretty confused when it comes to how to do this, the commands seem confusing to me.
So, if I had a php string, that contained html, how would I insert this into my DB using bind parameters?
I wanted to INSERT it, how would I do this?
$str = '<div id="test">Test string in db</div> string content';
I was told to use something like:
$rs = $db->Execute('select * from table where val=?', array('10'));
I haven't used ADODB for a while but I believe this should work, no?
$str = '<div id="test">Test string in db</div> string content';
$rs = $db->Execute('select * from table where val=?', array($str));
The ?'s in the SQL serve as placeholders for values that are bound to the statement.
When executed, ADO is executing (given your example)
select * from table where val=10
You should be able to construct your insert SQL roughly as:
INSERT INTO `table` (`col1`, `col2` ...) VALUES(?, ? ...)
Passing in your values (in the correct order) will render the appropriate query.
Using mysql_real_escape_string should do the trick too, it escapes the quotes automatically after which you can insert data into the database, consider this example:
$str = '<div id="test">Test string in db</div> string content';
$str_escaped = mysql_real_escape_string($str);
Now you can safely use the $str_escaped variable to insert data into the database. Furthermore, it is useful in preventing SQL injection attacks.
Adapted from the CodeIgniter framework:
function compile_binds($sql, $binds)
{
if (strpos($sql, '?') === FALSE)
{
return $sql;
}
if ( ! is_array($binds))
{
$binds = array($binds);
}
// Get the sql segments around the bind markers
$segments = explode('?', $sql);
// The count of bind should be 1 less then the count of segments
// If there are more bind arguments trim it down
if (count($binds) >= count($segments)) {
$binds = array_slice($binds, 0, count($segments)-1);
}
// Construct the binded query
$result = $segments[0];
$i = 0;
foreach ($binds as $bind)
{
$result .= mysql_real_escape_string($bind);
$result .= $segments[++$i];
}
return $result;
}
Then you could have a function:
function query($sql, $binds)
{
return $db->Execute(compile_binds($sql, $binds));
}
$query = query('select * from table where val=?', array('10'));