I'm working on a project using mysqli library, and I have reached the point where I need to create a SELECT query inside a method, depending on the parameters that were sent.
The behavior I'm looking for is similar to Android's SQLite where you pass the columns as a parameter and the values as the next paramenter.
I know I could create the query string if the parameter sent where the columns and the values, by iterating over them and then manually concatenating strings to a final query string, but I wonder if there is any core library that let you do this or any other way
You should use PDO prepared statements
//$param = array(":querycon1" => "querycon", ":querycon2" => "querycon2");
// $condition = "abc=:querycon1 AND xyz=:querycon2";
protected function setQuery($column,$condition,$param){
$this->query = "SELECT $column FROM tablename WHERE $condition";
$this->param = $param //
$this->getQuery($this->query, $this->param); // to a method that processes the query
}
Related
How can I make the database entry code work? Right now nothing shows when I run this code.
public function hookActionValidateOrder()
{
$products=[];
$ids_product_attribute=[];
$references=[];
$stocks= [];
for($i=0;Context::getContext()->cart->getProducts()[$i]['id_product'];$i++)
{
array_push($products,Context::getContext()->cart->getProducts()[$i]['id_product']);
array_push($ids_product_attribute,Context::getContext()->cart->getProducts()[$i]['id_product_attribute']);
array_push($references,Context::getContext()->cart->getProducts()[$i]['reference']);
array_push($stocks,Context::getContext()->cart->getProducts()[$i]['stock_quantity']);
die(Db::getInstance()->execute("
INSERT INTO cart_log (products, ids_product_attribute, references, stocks, time)
VALUES ($products[$i], $ids_product_attribute[$i], $references[$i], $stocks[$i])"));
}
// var_dump($products);
// var_dump($ids_product_attribute);
// var_dump($references);
// var_dump($products);
}
I would write this function this way:
public function hookActionValidateOrder()
{
$defaults = [
'id_product' => null,
'id_product_attribute' => null,
'reference' => 0,
'stock_quantity' => 0
];
$stmt = Db::getInstance()->prepare("
INSERT INTO cart_log
SET products = :id_product,
ids_product_attribute = :id_product_attribute,
references = :reference,
stocks = :stock_quantity");
$products = Context::getContext()->cart->getProducts();
foreach ($products as $product) {
$values = array_merge($defaults, array_intersect_key($product, $defaults));
$stmt->execute($values);
}
}
This example shows usage of query parameters in a prepared statement, so you don't try to interpolate PHP variables directly into your SQL query. Query parameters make it easier to write error-free SQL code, and they protect you from accidental SQL injection errors (also malicious attacks).
I recommend calling your cart->getProducts() once, and save the result in a local variable. I'm not sure what that function does, but I suppose it's running another SQL query. You shouldn't run the same SQL query many times for each loop, that will increase load to your database server.
The business about array_merge(array_intersect_key()) is to make sure the values array has both all of the needed keys, and no other keys besides the needed keys. Then it can be passed as-is to PDOStatement::execute().
I'm using an alternative form for INSERT that is supported by MySQL, using SET column = value ... syntax. I find this makes it easier to make sure I've matched a column to each value and vice-versa.
As mentioned in the comments above, you must enable exceptions in the database connector. I'm assuming your getInstance() function returns a PDO connection, so you can pass the array of parameters to execute(). You need to enable PDO exceptions as described here: https://www.php.net/manual/en/pdo.error-handling.php
If you don't enable exceptions, you should check the return value of prepare() and execute() to see if either is === false, and if so then log the errorInfo() (read the PHP doc I linked to about error handling).
I detected two error in your query:
You insert 5 fields, but only 4 values.
In value you don't add '' char if values string
Example: if all fields with data type string
VALUES ('$products[$i]', '$ids_product_attribute[$i]', '$references[$i]', '$stocks[$i]', 'Field 5')
I'm not finding much documentation-wise beyond some sources saying Query Builder statements are prepared, and others saying they are but not bound, then some saying they are bound etc. A solid answer would be much appreciated.
Furthermore, if I wanted to have my form data passed through into an array that I'm storing in my database, how should my following code be modified?
$user_first = $this->input->post('user_first');
$data['user_first'] = $user_first;
//this above code works fine if I want to store each part of the form
//in the array individually
$data = array(
'user_first' => 'My title'
//How can I get 'user_first' to => $user_first?
);
$this->pdo->insert('users', $data);
Thank you.
A few ways
//adding name by name to an array
$data = array('user_first' => $this->input->post('user_first'));
adding the entire post array
//as u have the same "name" in the form than the array u are sending to the db insert method
$data = $this->input->post();
//in short $this->input->post() is $_POST array, but cleaned
//or getting the values from $_POST
$data = array('user_first' => $_POST['user_first']);
Hope my answer helps u.
The answer depends to a large extent on what "prepared" means. "Binding" can be accomplished in a way very much like PDO. However, there are no methods that correspond to PDOStatement::bindColumn, PDOStatement::bindParam, or PDOStatement::bindValue.
The most direct equivalent to PDO::prepare() with "binding" would be as follows
$sql = "SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?";
$this->db->query($sql, array(3, 'live', 'Rick'));
The ? placeholders are replaced with the values in the array in the order they appear in the array. The input values will be escaped. The query() method does not support the PDO sytax of :name as a placeholder. (CI documentation on Query Binding.)
In general the various Query Builder methods combine to achieve the same overall effect as PDO::prepare() and PDOStatement::execute().
The functionality of PDOStatement methods to retrieve queried data (e.g. execute(), fetch(), etc.) are accomplished by calls to CI database methods for "Generating Query Results".
Assuming the three input from my example above have been posted by a here's how I would accomplish inserting them in a table
$data['id'] = $this->input->post('id');
$data['status'] = $this->input->post('status');
$data['author'] = $this->input->post('author');
$this->db-insert('some_table', $data);
If the element names are an exact match for the table column names and we know only those inputs will be posted the above could be simplified to
$this->db-insert('some_table', $this->input->post());
Using MySQL and PHP, a typical (PDO) query looks like this:
// prepare the query
$q = $DB->prepare("SELECT * FROM table_name WHERE property = :value");
// run the query
$q->execute(array(':value'=>$value));
This is safe from SQL injection, as the property value is treated separately to the query.
However if I want to use the same code to write a query that might retrieve a different field or different grouping, you cannot use the prepare/execute method alone, as you cannot use PDO parameters for fields (see here).
Can you simply use in_array() to check a field name, like this:
// return false if the field is not recognised
if(! in_array($field_name, array('field1','field2','field3')) return false
// run the query
$q = $DB->query("SELECT * FROM table_name ORDER BY " . $field_name);
Is there a safer / quicker way?
Already seems quite fast and secure. Maybe add backticks around the field name in the query.
To speed it up slightly you can use an assoc array and just check if the index exists instead of searching the contents of an array.
$fields = array('field1' => null, 'field2' => null, 'field3' => null);
if (!array_key_exists($field_name, $fields)) return false;
furthermore isset is faster than array_key_exists
if (!isset($fields[$field_name])) return false;
function benchmarks
I have a problem with a question mark parameter in a prepared statement using PDO. My Query class looks like this (for now, I'm still adding functionality like data limits, custom parameters filtering and automatic detection of supported statements for the driver being used):
// SQL query
class Query {
public $attributes;
// constructor for this object
public function __construct() {
if ($arguments = func_get_args()) {
$tmp = explode(" ", current($arguments));
if (in_array(mb_strtoupper(current($tmp)), ["ALTER", "DELETE", "DROP", "INSERT", "SELECT", "TRUNCATE", "UPDATE"], true)) {
// classify the query type
$this->attributes["type"] = mb_strtoupper(current($tmp));
// get the query string
$this->attributes["query"] = current($arguments);
// get the query parameters
if (sizeof($arguments) > 1) {
$this->attributes["parameters"] = array_map(function ($input) { return (is_array($input) ? implode(",", $input) : $input); }, array_slice($arguments, 1, sizeof($arguments)));
}
return $this;
}
}
}
}
This is the code fragment which executes the query:
$parameters = (!empty($this->attributes["queries"][$query]->attributes["parameters"]) ? $this->attributes["queries"][$query]->attributes["parameters"] : null);
if ($query = $this->attributes["link"]->prepare($this->attributes["queries"][$query]->attributes["query"], [\PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY])) {
if ($query->execute((!empty($parameters) ? $parameters : null))) {
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
}
And this is how I call it in my test code:
$c1->addQuery("lists/product-range", "SELECT * FROM `oc_product` WHERE `product_id` IN (?);", [28, 29, 30, 46, 47]);
if ($products = $c1->execute("test2")) {
foreach ($products as $product) {
print_r($product);
}
}
The problem I have is I just see the first product (it's a test against a vanilla OpenCart installation) with id 28. As you can see in my code, if the passed parameter is an array, it gets automatically detected by the lambda I have in place in the Query class constructor, so it gets rendered as a string like 28,29,30,46,47.
Is there a missing parameter in PDO setup I'm missing? Or maybe there's some bug or platform limit in what I'm doing? I know there's some limitations on what PDO can do in regards to arrays, and that's why I pre-implode all arrays for them to be passed like a simple string.
There's some procedures I've seen here in SO which, basically, composes the query string like WHERE product_id IN ({$marks}), where $marks is being dynamically generated using a procedure like str_repeat("?", sizeof($parameters)) but that's not what I'm looking for (I could resort to that in case there's no known alternative, but it doesn't look like a very elegant solution).
My development environment is composed of: Windows 7 x64, PHP 5.4.13 (x86, thread-safe), Apache 2.4.4 (x86) and MySQL 5.6.10 x64.
Any hint would be greatly appreciated :)
A ? placeholder can only substitute for a single literal. If you want an IN clause to accept an arbitrary number of values, you must prepare a new query for each possible length of your array.
E.g., if you want to select ids in array [1, 2], you need a query that looks like SELECT * FROM tbl WHERE id IN (?,?). If you then pass in a three-item array, you need to prepare a query like SELECT * FROM tbl WHERE id IN (?,?,?), and so on.
In other words, you cannot know with certainty what query you want to build/create until the moment you have the data you want to bind to the prepared statement.
This is not a PDO limitation, it is fundamental to how prepared queries work in SQL databases. Think about it--what datatype would the ? be in SQL-land if you said IN ? but had ? stand in for something non-scalar?
Some databases have array-types (such as PostgreSQL). Maybe they can interpret IN <array-type> the same way as IN (?,?,...) and this would work. But PDO has no way of sending or receiving array-type data (there is no PDO::PARAM_ARRAY), and since this is an uncommon and esoteric feature it's unlikely PDO ever will.
Note there is an extra layer of brokenness here. A normal database, when faced with the condition int_id = '1,2,3,4' would not match anything since '1,2,3,4' cannot be coerced to an integer. MySQL, however, will convert this to the integer 1! This is why your query:
$pstmt = $db->prepare('SELECT * FROM `oc_product` WHERE `product_id` IN (?)');
$pstmt->execute(array('28,29,30,46,47'));
Will match product_id = 28. Behold the insanity:
mysql> SELECT CAST('28,29,30,46,47' AS SIGNED INTEGER);
+------------------------------------------+
| CAST('28,29,30,46,47' AS SIGNED INTEGER) |
+------------------------------------------+
| 28 |
+------------------------------------------+
1 rows in set (0.02 sec)
Lambda detects an array and creates coma delimited string from it, and passed argument is treated as string, so the query looks like:
SELECT * FROM tbl WHERE id IN('1,2,3,4')
'1,2,3,4' is one string value for SQL.
If you are expecting only numerical values, you can omit adding them as parameters and simply put them in the query:
$a = [28, 29, 30, 46, 47];
$s = "SELECT * FROM tbl WHERE id IN(".implode(',', array_map('intval', $a)).")";
For different data types, you have to add as many parameter placeholders as you need, and bind every parameter separately.
OK here's the problem:
I'm trying to write the query function for a database class in my project and I want to make it easier to escape the sql and check if it is harmful to the database in anyway.
Let's say I have a query like this:
INSERT INTO users (id,name,email,username,birthdate)
VALUES(1,'Josh','josh101#coolsite.com','josh101','1978-11-02')
But it won't really help If I hardcode this into the function. So lets say I used a question mark for all the values I want to insert and then pass an array to the function containing the actual values I want to replace, just like the way it's done in codeigniter.
Here's a sample:
//Here's the way the function appears in the class definition.
public function query($sql,$params=array()){
if (!empty($params) && is_string($sql)):
//do some stuff here.
elseif (empty($params) && is_string($sql)):
//do some other stuff here.
else:
//bad sql argument.
die("Mysql_database ERROR: The query submitted is not a string!");
endif;
}
//Here's where the function is applied.
$sql="INSERT INTO users (id,name,email,username,birthdate)
VALUES(?,?,?,?,?)";
$params=array(1,'Josh','josh101#coolsite.com','josh101','1978-11-02');
$db= new Mysql_database();
$response=$db->query($sql,$params);
Now here's what I want to do:
If the second argument is not provided, just run the query as it is.
Else check the elements of the array provided for their type and escape them properly then replace them in their appropriate positions in the pseudo-sql string provided.
The problem is that it seems that all the question marks get replaced by only the first element of the array:
Here's the code:
/*assuming I already have a function called create_array that well,
basically creates an array with n elements
specified in the first parameter and fills each element with the value provided in
the second parameter.*/
$toreplace = create_array(substr_count($sql, "?"),"?");
$sqlComplete = str_replace($toreplace, $params, $sql);
If I echo $sqlComplete I get this:
INSERT INTO users (id,name,email,username,birthdate)
VALUES(1,1,1,1,1)
What can I do so that each element of $params is put in its appropriate position in the sql string?
PS: Please don't tell me to just use codeigniter because I'm trying to challenge myself here a bit by building a project from scratch, I don't want to always depend on frameworks to get the job done.
Maybe just use MySQL prepared statements?
It can be done like this:
$params=array(1,'Josh','josh101#coolsite.com','josh101','1978-11-02');
$sql="INSERT INTO users (id,name,email,username,birthdate)
VALUES(?,?,?,?,?)";
foreach($params as $param)
{
$pos = strpos($sql, '?');
if($pos !== false)
{
$sql = substr_replace($sql,"'" . $param . "'",$pos,1);
}
}
echo $sql;
Outputs
INSERT INTO users (id,name,email,username,birthdate) VALUES('1','Josh','josh101#coolsite.com','josh101','1978-11-02')
This doesn't do any escaping, it just populates the values in the query. You'll need to add the escaping that's appropriate to the framework/DB API.