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);
Related
I created a series of helper functions in php that I have been using because they make prepared mysqli statements a lot quicker and easier to follow for me. They have worked on other projects but for some reason the problem on this particular project is eluding me... It works perfectly offline but when I upload it to my server I get 0 rows returned. I am assuming it must be a setting somewhere but I can't for the life of me figure out what it is. No errors in the Apache log appear when I run this.
In this particular case I am trying to verify that a series of values exists in the database. If they do, return true. If they don't, return false.
Here is the helper function:
function verifyRow($a_verify) {
//Pass $mysqli connection variable
global $mysqli;
//Create a string from the selection criteria
$selection = implode(", ", (array) $a_verify->selection);
//Transfer table contents to table variable just for consistency
$table = $a_verify->table;
//Create a string from the parameter types
$type = implode("", (array) $a_verify->type);
//Push a reference to the string of types to the parameter array
$a_params[] =& $type;
//For each result parameter, push a column = result string into the colvals array and create a reference to the parameter in the parameters array
foreach ($a_verify->columns as $key => $value) {
$a_colvals[] = "{$value} = ?";
$a_params[] =& $a_verify->results->$key;
}
//Create a string from the column = result array
$colvals = implode(" AND ", $a_colvals);
//Generate a query
$query = "SELECT {$selection} FROM {$table} WHERE {$colvals} LIMIT 1";
//Prepare statement and error checking
if (!($stmt = $mysqli->prepare($query))) {
print "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
//Bind parameters and error checking
if (!(call_user_func_array(array($stmt, 'bind_param'), $a_params))) {
print "Binding parameters failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
//Execute statement and error checking
if (!$stmt->execute()) {
print "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
//Get the result
$result = $stmt->get_result();
//Bind the result to a variable
$row = $result->fetch_assoc();
//If the row exists, return true
if ($row) {
return true;
}
//If the row doesn't exist, return false
else {
return false;
}
}
Here is how I am trying to retrieve the result:
// Confirm there is a user in the db with this user code
$a_verify = (object) array(
"selection" => (object) array(
"*"
),
"table" => "`users`",
"columns" => (object) array(
"`code`"
),
"results" => (object) array(
"'123456'"
),
"type" => (object) array(
"s"
)
);
print verifyRow($a_verify); //Ideally 'true'
Here is how the "users" database is set up:
|-----|---------------|--------|
| id | email | code |
--------------------------------
| 0 | test#test.com | 123456 |
--------------------------------
Any idea what the problem might be? It's driving me crazy.
Helper functions are great and here I am totally with you. But... you call this dazzling skyscraper of code "a lot quicker and easier" than vanilla SQL for real?
For some reason you are constantly moving your code back and forth from one format into another. For example, you are creating an array array("*") then convert it into object (object) and then... convert it back into array (array) $a_verify->selection. Why?
Or you take a simple SQL query, SELECT * FROM users WHERE code = ? and create a multiple-line construction where SQL keywords are substituted with array keys with an addition of a lot of quotes, parentheses and stuff. And then convert it back into SQL.
SQL is an amazing language, it survived for decades thanks to its simplicity and strictness. There is no reason to dismantle it into pieces to add a structure. SQL already has a structure. And it has many features that just aren't supported by your helper. In the end, with so much effort you get a severely limited SQL version with complicated syntax and entangled implementation. You call it a helper function?
Last but not least. Such functions where you are adding column and table names freely are asking for SQL injection. And your other question displays this appalling practice - you are adding the user input directly in your SQL. Why bother with prepared statements if you have an injection anyway? See this answer on how to deal with such situations.
Let me show you a real helper function. It makes your code short and readable and it supports full features of SQL (ordering the results for example):
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;
}
If you're interesting in how it works you can read the explanation I wrote here.
Now let's rewrite your helper function based on mine
function verifyRow($mysqli, $sql, $parameters) {
$result = prepared_query($mysqli, $sql, $parameters)->get_result();
return (bool)$result->fetch_assoc();
}
Just two lines of code!
And now to verify the actual row:
var_dump(verifyRow($mysqli, "SELECT 1 FROM users WHERE code = ?", ['12345']));
One line instead of a dozen.
Now to the question why it doesn't find anything. There are two possible reasons.
indeed there is some misconfiguration.
a much simpler case: there is no such data in the table you are querying at the moment
To test for the former you must enable PHP error reporting. If your code doesn't work as expected then there must be an error somewhere and all you need is to catch it. Add these two lines to your code
error_reporting(E_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
ini_set('log_errors',1); // well to be 100% sure it will be logged
in order to make sure that all PHP and mysql errors will present themselves. Then run the code again. If it won't produce any error then likely your system is configured properly.
Now to test your data. Write a different query that would return all rows from the table
$sql = "SELECT code FROM users WHERE 1 = ?";
$result = prepared_query($mysqli, $sql, [1])->get_result();
var_dump($result->fetch_all(MYSQLI_ASSOC));
and see what you really have in your database. Probably there are no rows or a different code value. A hint: there could be non-printable characters that would prevent the matching. to reveal them use a very handy function rawurlencode(): it will leave all basic characters as is but convert everything else into hex codes making them an easy spot
So I am attempting to write a generic sqlite insert that can be used no matter how many items a row has. This is for a single row, assumes all columns other than ID, which is set to autoincrementing integer, are assigned, and bindparam must be used. I have been attempting it like so:
Table Quarterbacks
ID---firstName---lastName
public static function insert($table, $values)
{
$pdo = new PDO('sqlite:testTable.sqlite');
$inputString = implode(",", $values);
$statement = $pdo->prepare("insert into $table values (:value)");
$statement->bindParam(':value', $inputString);
$statement->execute();
}
$new = array("Steve", "Young");
Query::insert("Quarterbacks", $new);
The idea being that the table will now add a new row, increment the ID, and add Steve Young. But I get the generic error that the prepare statement is false. I know my pdo is connecting to the database, as other test methods work. There's a lot of array related threads out there but it seems like they're much more complicated than what I'm trying to do. I'm pretty sure it has to do with it treating the single string as invalid, but it also won't take an array of strings for values.
Edit:I'm starting to lean towards a compromise like bash, ie provide a large but not infinite amount of function parameters. Also open to the ...$ style but I feel like that ends up with the same problem as now.
I was able to get this to work
$name = "('daunte' , 'culpepper')";
$cats = "('firstName', 'lastName')";
$statement = $pdo->prepare("insert into $table" .$cats ." values" .$name);
$statement->execute();
But not this
$name = "('reggie' , 'wayne')";
$cats = "('firstName', 'lastName')";
$statement = $pdo->prepare("insert into $table:cats values:name");
$statement->bindParam(':cats', $cats);
$statement->bindParam(':name', $name);
$statement->execute();
How would I use MySQL's NOW() in PDO prepared statements, or how would I workaround using it while keeping in mind that possibly the Apache Server and Database Server might have either a slightly current time mismatch (few seconds), or in rare occasions might be timezones apart?
I have the following function in my code:
try {
$dbh->insert("users", array(
"email" => $email,
"password" => $password,
"salt" => $salt,
"ingame" => $ingame,
"kiosk" => $kiosk
));
} catch (PDOException $ex) {
error($ex);
}
Which calls:
/**
* Inserts data into a table. Data must be given in key-value pairs.
*
* Example: $dbh->insert("table", array(
* "data1" => $data1,
* "data2" => $data2
* );
*
* #param type $table The table to insert to
* #param type $keyvaluepairs The key-value pairs.
* #return type The statement that this query produced.
*/
public function insert($table, $keyvaluepairs) {
$sql = "INSERT INTO `{$table}` (";
$values_sql = ") VALUES(";
$values = array();
foreach ($keyvaluepairs as $key => $value) {
$sql .= "`${key}`, ";
$values_sql .= "?, ";
$values[] = $value;
}
$query = substr($sql, 0, -2).substr($values_sql, 0, -2).")";
return $this->query($query, $values);
}
Which calls:
//TODO update documentation to show it also handles associative arrays with bindvalue
/**
* Can be called to create a query. Use either unnamed or named placeholders for the prepared statements.
*
* Example: $dbh->query("INSERT INTO table (data1, data2) VALUES(?, ?)", array($data1, $data2));
*
* #param type $query The input query, including unnamed or named placeholders
* #param type $values The input values. If it's not an array, then it will be an one-element array
* #return type The statement constructed by this query
*/
public function query($query, $values = array()) {
if (!is_array($values)) {
$values = array($values);
}
$statement = $this->dbh->prepare($query);
$statement->setFetchMode(PDO::FETCH_OBJ);
$i = 1;
if (is_assoc($values)) {
foreach ($values as $key => $value) {
$statement->bindValue($key, $value);
}
}
else {
foreach ($values as $value) {
$statement->bindValue($i++, $value);
}
}
$statement->execute();
return $statement;
}
Where I have the function:
function is_assoc($array) {
return (bool)count(array_filter(array_keys($array), 'is_string'));
}
So the deal here is that I cannot use custom MySQL queries for the inserts as I've encapsulated those for the sake of easyness, but I still want to be able to insert NOW() without making use of TIMESTAMP / CURRENT_TIMESTAMP().
I hope you understand that this question requires an explained answer as I have already read the 'normal' answers and shown that they do not satisfy my needs.
UPDATE: I have added const SQL_NOW = 1; to my DBH class, however now I want to modify the insert to something like this:
public function insert($table, $keyvaluepairs) {
$sql = "INSERT INTO `{$table}` (";
$values_sql = ") VALUES(";
$values = array();
foreach ($keyvaluepairs as $key => $value) {
if ($value == SELF::SQL_NOW) {
$sql .= "NOW(), ";
}
else {
$sql .= "`${key}`, ";
$values_sql .= "?, ";
$values[] = $value;
}
}
$query = substr($sql, 0, -2).substr($values_sql, 0, -2).")";
return $this->query($query, $values);
}
Which may be a suitable solution, however I cannot use 1 as SQL_NOW value, as it would then fail if I'd want to insert an integer 1. If I go with this solution, what value would SQL_NOW then have? Is it even possible to give it no value?
Excellent question!
This is a perfect example that clearly shows why all these numerous insert(), update() and all other stuff, intended to substitute SQL, are wrong by design.
NOW() is not the only issue you will face with. Just because SQL is not that silly a language as it seems at first glance. And it was invented on purpose. And it's reliability was proven for decades. Means it is not that easy to write whole SQL just as an exercise while learning PHP.
So, the best thing you could do is to keep SQL as is.
What you really, really need is a helper function or two. To automate the repetitive tasks. That is ALL. While SQL have to be left as is. Which will let you to use ALL it's power including use of functions, functions with arguments(!), query modifiers, such as 'INSERT IGNORE' or extended syntax like JOINS.
In case you are using PDO, it is not that simple but feasible.
However, the only proper solution is to use a placeholder of the special type for the SET statement.
$data = array(
"email" => $email,
"password" => $password,
"salt" => $salt,
"ingame" => $ingame,
"kiosk" => $kiosk,
);
$dbh->query("INSERT INTO ?n SET reg = NOW(), ?u","users", $data);
Just one single line to solve all that mess and many, many, many other issues.
Just one single query() method to run any query you want, even REPLACE INTO.
Update.
Just look what are you doing!
You were planning your class to simplify things. But at the moment you are making it more and more complex. In the end you will have a hulking giant which inconsistent syntax for the numerous exceptions scarcely understood even by it's creator and noone else. And which still don't let you run some queries.
Please rethink your design before it's too late.
You can write a wrapper that does this if you pass it an additional argument containing all the column=>SQLfunction values you need.
Mine looks like this:
function pInsertFunc($action, $table, $values, $sqlfunctions)
{
global $pdb;
// There's no way to pass an SQL function like "NOW()" as a PDO parameter,
// so this function builds the query string with those functions. $values
// and $sqlfunctions should be key => value arrays, with column names
// as keys. The $values values will be passed in as parameters, and the
// $sqlfunction values will be made part of the query string.
$value_columns = array_keys($values);
$sqlfunc_columns = array_keys($sqlfunctions);
$columns = array_merge($value_columns, $sqlfunc_columns);
// Only $values become ':paramname' PDO parameters.
$value_parameters = array_map(function($col) {return (':' . $col);}, $value_columns);
// SQL functions go straight in as strings.
$sqlfunc_parameters = array_values($sqlfunctions);
$parameters = array_merge($value_parameters, $sqlfunc_parameters);
$column_list = join(', ', $columns);
$parameter_list = join(', ', $parameters);
$query = "$action $table ($column_list) VALUES ($parameter_list)";
$stmt = $pdb->prepare($query);
$stmt->execute($values);
}
Use it like this:
$values = array(
'ID' => NULL,
'name' => $username,
'address' => $address,
);
$sqlfuncs = array(
'date' => 'NOW()',
);
pInsertFunc("INSERT INTO", "addresses", $values, $sqlfuncs);
The query string that results looks like this:
INSERT INTO addresses (ID, name, address, date) VALUES (:ID, :name, :address, NOW())
I have an array like this
$a = array( 'phone' => 111111111, 'image' => "sadasdasd43eadasdad" );
When I do a var-dump I get this ->
{ ["phone"]=> int(111111111) ["image"]=> string(19) "sadasdasd43eadasdad" }
Now I am trying to add this to the DB using the IN statement -
$q = $DBH->prepare("INSERT INTO user :column_string VALUES :value_string");
$q->bindParam(':column_string',implode(',',array_keys($a)));
$q->bindParam(':value_string',implode(',',array_values($a)));
$q->execute();
The problem I am having is that implode return a string. But the 'phone' column is an integer in the database and also the array is storing it as an integer. Hence I am getting the SQL error as my final query look like this --
INSERT INTO user 'phone,image' values '111111111,sadasdasd43eadasdad';
Which is a wrong query. Is there any way around it.
My column names are dynamic based what the user wants to insert. So I cannot use the placeholders like :phone and :image as I may not always get a values for those two columns. Please let me know if there is a way around this. otherwise I will have to define multiple functions each type of update.
Thanks.
Last time I checked, it was not possible to prepare a statement where the affected columns were unknown at preparation time - but that thing seems to work - maybe your database system is more forgiving than those I am using (mainly postgres)
What is clearly wrong is the implode() statement, as each variable should be handled by it self, you also need parenthesis around the field list in the insert statement.
To insert user defined fields, I think you have to do something like this (at least that how I do it);
$fields=array_keys($a); // here you have to trust your field names!
$values=array_values($a);
$fieldlist=implode(',',$fields);
$qs=str_repeat("?,",count($fields)-1);
$sql="insert into user($fieldlist) values(${qs}?)";
$q=$DBH->prepare($sql);
$q->execute($values);
If you cannot trust the field names in $a, you have to do something like
foreach($a as $f=>$v){
if(validfield($f)){
$fields[]=$f;
$values[]=$v;
}
}
Where validfields is a function that you write that tests each fieldname and checks if it is valid (quick and dirty by making an associative array $valfields=array('name'=>1,'email'=>1, 'phone'=>1 ... and then checking for the value of $valfields[$f], or (as I would prefer) by fetching the field names from the server)
SQL query parameters can be used only where you would otherwise put a literal value.
So if you could see yourself putting a quoted string literal, date literal, or numeric literal in that position in the query, you can use a parameter.
You can't use a parameter for a column name, a table name, a lists of values, an SQL keyword, or any other expressions or syntax.
For those cases, you still have to interpolate content into the SQL string, so you have some risk of SQL injection. The way to protect against that is with whitelisting the column names, and rejecting any input that doesn't match the whitelist.
Because all other answers allow SQL injection. For user input you need to filter for allowed field names:
// change this
$fields = array('email', 'name', 'whatever');
$fieldlist = implode(',', $fields);
$values = array_values(array_intersect_key($_POST, array_flip($fields)));
$qs = str_repeat("?,",count($fields)-1) . '?';
$q = $db->prepare("INSERT INTO events ($fieldlist) values($qs)");
$q->execute($values);
I appreciated MortenSickel's answer, but I wanted to use named parameters to be on the safe side:
$keys = array_keys($a);
$sql = "INSERT INTO user (".implode(", ",$keys).") \n";
$sql .= "VALUES ( :".implode(", :",$keys).")";
$q = $this->dbConnection->prepare($sql);
return $q->execute($a);
You actually can have the :phone and :image fields bound with null values in advance. The structure of the table is fixed anyway and you probably should got that way.
But the answer to your question might look like this:
$keys = ':' . implode(', :', array_keys($array));
$values = str_repeat('?, ', count($array)-1) . '?';
$i = 1;
$q = $DBH->prepare("INSERT INTO user ($keys) VALUES ($values)");
foreach($array as $value)
$q->bindParam($i++, $value, PDO::PARAM_STR, mb_strlen($value));
I know this question has be answered a long time ago, but I found it today and have a little contribution in addition to the answer of #MortenSickel.
The class below will allow you to insert or update an associative array to your database table. For more information about MySQL PDO please visit: http://php.net/manual/en/book.pdo.php
<?php
class dbConnection
{
protected $dbConnection;
function __construct($dbSettings) {
$this->openDatabase($dbSettings);
}
function openDatabase($dbSettings) {
$dsn = 'mysql:host='.$dbSettings['host'].';dbname='.$dbSettings['name'];
$this->dbConnection = new PDO($dsn, $dbSettings['username'], $dbSettings['password']);
$this->dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
function insertArray($table, $array) {
$fields=array_keys($array);
$values=array_values($array);
$fieldlist=implode(',', $fields);
$qs=str_repeat("?,",count($fields)-1);
$sql="INSERT INTO `".$table."` (".$fieldlist.") VALUES (${qs}?)";
$q = $this->dbConnection->prepare($sql);
return $q->execute($values);
}
function updateArray($table, $id, $array) {
$fields=array_keys($array);
$values=array_values($array);
$fieldlist=implode(',', $fields);
$qs=str_repeat("?,",count($fields)-1);
$firstfield = true;
$sql = "UPDATE `".$table."` SET";
for ($i = 0; $i < count($fields); $i++) {
if(!$firstfield) {
$sql .= ", ";
}
$sql .= " ".$fields[$i]."=?";
$firstfield = false;
}
$sql .= " WHERE `id` =?";
$sth = $this->dbConnection->prepare($sql);
$values[] = $id;
return $sth->execute($values);
}
}
?>
dbConnection class usage:
<?php
$dbSettings['host'] = 'localhost';
$dbSettings['name'] = 'databasename';
$dbSettings['username'] = 'username';
$dbSettings['password'] = 'password';
$dbh = new dbConnection( $dbSettings );
$a = array( 'phone' => 111111111, 'image' => "sadasdasd43eadasdad" );
$dbh->insertArray('user', $a);
// This will asume your table has a 'id' column, id: 1 will be updated in the example below:
$dbh->updateArray('user', 1, $a);
?>
public function insert($data = [] , $table = ''){
$keys = array_keys($data);
$fields = implode(',',$keys);
$pre_fields = ':'.implode(', :',$keys);
$query = parent::prepare("INSERT INTO $table($fields) VALUES($pre_fields) ");
return $query->execute($data);
}
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)...