Selecting columns from array in Doctrine QueryBuilder - php

I want to select the columns given in an array and then group based on those columns but I can't figure out how to pass array values through select() in Doctrine. I've tried a bunch of different variations of this and I can't get it to work. $factors is just a standard array with column names as values.
$qb->select(":factors")
->from("Table")
->where("type = :type")
->groupBy(":factors")
->setParameter("factors", $factors)
->setParameter("type", $type);

Parameters are not ment to be used in a SELECT statement. Read about prepared statements in PDO, Doctrine is just using this functionallity.
They are only used to compare with stored database values, not column or table names. Prepared statements basically help you escape those values used in a query, which could be a bit difficult for strings containing " or '.
If you want to use your $factors array for variable select statments you could just do
$qb->select(implode(',', $factors))
but in that case you have to prevent injections attacks by yourself. Best would be to have a whitelist of allowed values in $factors.
Same holds for GROUP BY statement.

Related

Should you use prepared statments on LIMIT

One day I was googling to understand when a prepared statment should be used. As I searched online many claimed that you should use always prepared statments. Now the question I have is... does this also count for LIMIT? I mean it is possible (I tried) but is it really that rational? Same question on ORDER BY too.
When the database does not allow you to use a parameter on a specific location of the SQL statement you need to assemble the query on the fly, by the use of Dynamic SQL. That is... concatenating strings to get a full functioning SQL query.
Now, the trick is to make it safe against SQL Injection. To do this:
Make sure the value for LIMIT is an integer, and not something unsafe coming right from the UI like 3; DROP TABLE EMPLOYEE.
For the ORDER BY clause make sure the columns are not coming from the UI "as is"; use some kind of projection. For example, if there are 50 columns to order by, the UI can display them all, but then just send a number (from 1 to 50) to the backend; the backend receives this number and reconstitutes the ordering column(s) from it.
Normally the LIMIT parameters must be literals, not values that can be substituted for placeholders, so you would have to validate that they're integers before substituting into the string.
However, if you use PDO rather than mysqli, it allows you to perform parameter substitution in the LIMIT clause, by using the PDO::ATTR_EMULATE_PREPARES option. This is automatically enabled for a specific prepared statement if it contains parameters in this clause. See How to apply bindValue method in LIMIT clause? for the specific details.

(PHP) I have an unknown number of key value pairs in an array, how can I use them in an sql query?

I am trying to make an inventory / invoice web application. The user enters information such as order ID, date, order total, and then each of the products bought along with their respective quantity. I'm using PDO for the sql queries.
I do not know in advance how many unique products are going to be in an invoice so I have an associative array that stores the products and their quantities (product name is used as the key) when the form is submitted.
On submit a prepared statement is built/executed.
Right now I have the order_id, date, and order_total query done.
$stmt = $connection->prepare("INSERT INTO table_1 (order_id, order_date, order_total) VALUES ('$orderid', '$date', '$total_cost')");
$stmt->execute();
That part is simple enough. The aim of the other query is the following.
$testStmt = $connection->prepare("INSERT INTO table_2 (keys from the assoc array are listed here) VALUES (values from the assoc arrays are listed here)");
$testStm->execute();
My array would end up looking like this once the user inputs some products:
$array
(
"product1" => quantity1
"product2" => quantity2
)
The idea I have had so far is to make a string for columns that need to be included in the sql query and then a string for the values for the sql query. Then iterate through the array and append the keys and values to the respective strings in such a way that I could use them in the sql query. I haven't gotten it to work and am worried that it could open myself up to sql injection (I am still quite unfamiliar with sql injection so I have been trying to read up on it).
$columns;
$values_input;
foreach($assoc_array as $product => $quant)
{
$columns .= "' " . $product . "', ";
$values_input .= "' " . $quant . "', ";
}
The idea being that $columns and $values_input string would end up containing all the appropriate column names and the quantities to be entered into those columns. Then I figured I could be able to use those strings as part of the SQL query. Something like this.
INSERT INTO $columns VALUES $values_input
I'd appreciate any help or insight. If I'm way off here or doing something in a retarded way feel free to shout about it, I'd rather fix a screw up than continue on with it if that's the case.
You are already using PDO, which is a good thing if you want to protect yourself from SQL injection. You are even trying to prepare your statement, but since you are not binding any parameters, one could argue if that is really what you are doing. Example 5 on the PHP docs page is in fact pretty close to what you want to do. Allow me to adapt it to your use case:
// create a placeholders string looking like "?, ?, ..., ?"
$placeholders = implode(',', array_fill(0, count($params), '?'));
// prepare the statement
$qry = $connection->prepare("INSERT INTO table_2 ($params) VALUES ($params)");
// bind the parameters to the statement. (We first need all columns, then all values)
$qry->execute(array_merge(array_keys($params), array_values($params)));
This should result in a query that looks exactly like your first example, but with a dynamic number of columns, or parameters. And since you are preparing your statement and binding the parameters on execution, PDO should handle all quoting and escaping to prevent SQL injection.
As a side note, your table structure seems a bit of to me. I don't think you normalized your data correctly, though it is a bit hard to tell with the table names you are using. I believe your structure should look something like this, and I fear it doesn't:
TABLE orders (id, date, total, client_id)
TABLE products (id, name, price, ...)
TABLE order_lines (id, order_id, product_id, quantity)
TABLE clients (...)
The exact structure obviously depends on your use case, but I believe this is about the simplest structure you can get away with if you want to build an order system that you can easily query and that can serve as a base for possible expansion in the future.
Since you are trying to make an inventory/invoice application, do you happen to have a product database? If you do, you may want to use the product id instead of the product names as key. As product names sound like there could be duplicates or can change. If product name changes, you will have problems querying.
Do you accept products not in db to be entered into the invoice? If so, it adds some complications.
On SQL injections, you should sanitize input before using it for queries. Read: What's the best method for sanitizing user input with PHP?
Most modern frameworks have many built in protections against SQL injections if you do not query manually. So consider using them.
Many of them use active record pattern see: http://www.phpactiverecord.org/projects/main/wiki/Basic_CRUD (So you don't have to deal with writing queries manually like you do.)
An example of active record in a framework: https://www.codeigniter.com/user_guide/database/query_builder.html

PDO prepared statement vs. query generated from array

I have a 2-dimensional array that I need to insert into MySQL table. I know I can either use PDO prepared statement and loop through the array to pass the values into the prepared statement or I can generate a huge sql statement looping through the array and using implode function on the array. I've seen both methods described in this forum. My question is, which one is faster, why would you use one method versus the other one.
Note: The array I will be inserting has thousands of rows which, if I use method 2 would generate a huge SQL statement. Is there any limit as to how long the sql statement can be?

Can PHP bind a list of numbers in a SQL prepared statement?

I am converting a Coldfusion website to PHP. I have a query that looks in a list of comma separated numbers to see if there is a match and then responds accordingly. This is the where statement I am currently using.
WHERE (`link_issue_category`.`Category_ID` IN (<CFQUERYPARAM value="#Category_id#" list = "yes">)
How do I write this in PHP?
CFQUERYPARAM does some validation on the value, and if possible sets up a bind parameter. You can probably just embed the value into the SQL, assuming you've already done validation / sanitization on it. The list parameter specifies that this is a comma-delimited list. You should be able to plug this list directly into the query, depending on the value type.
"WHERE (`link_issue_category`.`Category_ID` IN ($category_id)";
If your values in the list are strings, you may need to wrap them in qoutes before they go into the query.
FYI CF just creates a new prepared statement with the number of ? being the same as the length of your list behind the scene.
So if you want the same behaviour in PHP, it wouldn't be that hard really. Just dynamically create a new prepared statement, and bind them accordingly.
PHP Prepared statements: http://php.net/manual/en/pdo.prepared-statements.php
However, you could have just use regex to validate the list of values to numeric value and comma's only, and use the variable as part of the SQL statement.

Sending a list of values of the same field to MySQL stored procedure

I have a MySQL table with ID as a primary key and other for this matter non-important fields.
What I would like to do is delete multiple records by sending a list of IDs for deletion as a parameter to stored procedure.
I know how to do this manually (building a query directly in PHP) but I would like to avoid that and do all my SQL directly in the DB.
Tried searching SO but couldn't find any related questions. Sorry if this is a duplicate.
Thanks
In accordance to http://dev.mysql.com/doc/refman/5.1/en/faqs-stored-procs.html#qandaitem-B-4-1-17 you can't do it directly.
But I think you can try to the following trick:
Create string of you ids in php like 'id1,id2,id3'.
Use prepared statement for binding this sting on fly.
Maybe it helps.
You could try something like
DELETE FROM sometable WHERE FIND_IN_SET(idfield, #param)
no idea if this'd work (and don't have access to a mysql instance right now to test on). Basically the problem is that if you pass in a comma-separated value list into a paramter, it'll just be a string inside the sproc, and doing a WHERE id IN ('1,2,3') would fail, since that's just a simple string and not at all the same as WHERE id IN (1,2,3). The find_in_set() function should take care of that.
I gave +1 to #Marc B for clever use of FIND_IN_SET(). It won't be able to use an index, so the performance won't be good, but it should work.
Another solution that can work (but will be slow as well, because it can't use an index):
DELETE FROM sometable
WHERE CONCAT(',', param, ',') LIKE CONCAT('%,', idfield, ',%')
The solution that #Andrej L describes isn't really parameter binding, it's interpolation of a stored procedure argument into a dynamic SQL string prior to preparing it.
SET sql = CONCAT('DELETE FROM sometable WHERE idfield IN (', param, ')');
PREPARE stmt FROM sql;
EXECUTE stmt;
You can't parameterize a list of values with a single parameter, even if the parameter's value looks like a comma-separated list of integers.
Interpolation can work, and it will benefit from an index, but be careful to filter the string so it contains only numeric digits and commas. Otherwise you introduce a significant risk of SQL injection (debunking the claim that some people make that stored procedures are inherently more secure).

Categories