I'm trying to create an advanced search in php. The inputs are not required, users can decide if they want to search for a manufacturer or just set the minimum price, etc. I'm trying to save the "s" and "i" for the bind_param in an array, and the variables in another array, then implode them in the bind_param part.
This is where I got the problem. The $params implode works fine, but when I'm trying to implode the $vars array, I get the error message that says "Only variables should be passed by reference".
It's because if I push a variable to my array, it stores it's value and not the variable itself. I've tried to push them as strings, like '$example', but in this case, when I implode it, got the same message because it's a string.
So, how should I store them in the array to be able to use them in the bind_param?
In this example I show only 2 inputs, but ofc I have a lot more.
if ($_SERVER['REQUEST_METHOD'] == 'GET' && isset($_GET['search_button'])) {
$params[] = "i";
$vars[] = '$status';
$sql_search = 'SELECT m.*, u.premium, u.avatar FROM motorcycles m INNER JOIN users u ON u.id = m.userid WHERE status = ?';
if (isset($_GET['manufacturer_search']) && $_GET['manufacturer_search'] !== "") {
$manufacturer_search = $_GET['manufacturer_search'];
$sql_search .= " AND manufacturer LIKE ?";
array_push($params, 's');
array_push($vars, '$manufacturer_search');
}
if (isset($_GET['min_price']) && $_GET['min_price'] !== "") {
$min_price = $_GET['min_price'];
$sql_search .= " AND price >= ?";
array_push($params, 'i');
array_push($vars, '$min_price');
}
$sql_search .= " ORDER BY u.premium DESC LIMIT ?, ?";
array_push($params, 'ii');
array_push($vars, '$this_page_first_result', '$results_per_page');
$stmt_search = $link->prepare($sql_search);
$stmt_search->bind_param(implode("", $params), implode(",", $vars));
$stmt_search->execute();
$result = $stmt_search->get_result();
}
You should provide the variables you want separately as the last parameter of bind_params, what you are doing is creating a string of all your variables and passing that.
Change
$stmt_search->bind_param(implode("", $params), implode(",", $vars));
To
$stmt_search->bind_param(implode("", $params), ...$vars );
And PHP will take all entries inside your $vars array and pass them as separate parameters of the function.
For more information on this see the Documentation of bind_param, PHP's introduction of the splat operator here and here and some extra information on stack overflow.
Related
Is there any standard or best way in PHP PDO database class to change the operator from "=" to "IS" when I don't know if the value I'm going to pass is NULL or not. Here is an example of what I mean.
I have two variables. I want a count of the results that match. Sometimes one or both of the variables can be NULL. But MySQL requires a different operator to find NULL (IS NULL).
I'm currently just creating another variable for the operator and when I build the query I inject it in as so:
// First create a variable "=" or "is" depending on whether var1 and var2 are NULL or not
if($var1 == NULL) $var1_operator = "IS";
else $var1_operator = "=";
if($var2 == NULL) $var2_operator = "IS";
else $var2_operator = "=";
// Build the query
$query = 'SELECT count(*) as count FROM table
WHERE var1 '.$var1_operator.' :var1 AND var2 '.$var2_operator.' :var2';
// Conduct the query
$prepare = $db_connection->prepare($query);
$prepare->bindValue(':var1', $var1, PDO::PARAM_STR);
$prepare->bindValue(':var2', $var2, PDO::PARAM_STR);
$prepare->execute();
$results = $prepare->fetch();
Is there another way to do this? This works to bindValue using PARAM_STR even when the value is NULL and not really a string. The query returns the correct value count. Just not sure if there is better practice for doing it.
As you are using mysql, you can use its spaceship operator, <=>:
$query = 'SELECT count(*) FROM table WHERE var1 <=> ? AND var2 <=> ?';
$prepare = $db_connection->prepare($query);
$prepare->execute([$var1, $var2]);
$count = $prepare->fetchColumn();
But in general, if you need variable operators or other query parts, such a conditional query building is the only way.
A more generalized solution can be found in my article How to create a WHERE clause for PDO dynamically:
// always initialize a variable before use!
$conditions = [];
$parameters = [];
// conditional statements
if (!empty($_GET['name']))
{
// here we are using LIKE with wildcard search
// use it ONLY if really need it
$conditions[] = 'name LIKE ?';
$parameters[] = '%'.$_GET['name']."%";
}
if (!empty($_GET['sex']))
{
// here we are using equality
$conditions[] = 'sex = ?';
$parameters[] = $_GET['sex'];
}
if (!empty($_GET['car']))
{
// here we are using not equality
$conditions[] = 'car != ?';
$parameters[] = $_GET['car'];
}
// the main query
$sql = "SELECT * FROM users";
// a smart code to add all conditions, if any
if ($conditions)
{
$sql .= " WHERE ".implode(" AND ", $conditions);
}
// the usual prepare/execute/fetch routine
$stmt = $pdo->prepare($sql);
$stmt->execute($parameters);
$data = $stmt->fetchAll();
so in these conditions above whatever logic could be implemented, including operator change or anything.
This question already has answers here:
Build SELECT query with dynamic number of LIKE conditions as a mysqli prepared statement
(2 answers)
Closed 11 months ago.
I need to dynamically build up the SQL statement and the parameters based on user input. The length of the sql statement and the number of parameters changes based on user input. I am trying to use this tutorial and apply it to my code. Here is the code:
$query = "SELECT p.*, s.*
FROM product p
INNER JOIN product_shop ps
ON ps.p_id = p.p_id
INNER JOIN shop s
ON s.s_id = ps.s_id
WHERE s.country = ?";
$a_params[] = $place['country'];
$a_param_type[] = "s";
// prepare and bind
$param_type = '';
foreach ($place as $key => $value) {
if ($key === "country") {
continue;
}
$query .= " and s.$key = ?";
$a_params[] = $value;
$a_param_type[] = "s";
}
/* Prepare statement */
$stmt = $conn->prepare($query);
if($stmt === false) {
trigger_error('Wrong SQL: ' . $sql . ' Error: ' . $conn->errno . ' ' . $conn->error, E_USER_ERROR);
}
$a_params[] = $a_param_type;
/* use call_user_func_array, as $stmt->bind_param('s', $param); does not accept params array */
call_user_func_array(array($stmt, 'bind_param'), $a_params);
/* Execute statement */
$stmt->execute();
$meta = $stmt->result_metadata();
I know $place['country'] will always be populated. The sql statement is correct. It is:
"SELECT p.*, s.* \n FROM product p \n INNER JOIN product_shop ps \n ON ps.p_id = p.p_id \n INNER JOIN shop s \n ON s.s_id = ps.s_id \n WHERE s.country = ? and s.suburb = ? and s.city = ? and s.province = ?"
Don't mind the "\n" chars, they have no effect on the sql statement.
In:
call_user_func_array(array($stmt, 'bind_param'), $a_params);
the value of $a_params is:
0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:array(4)
0:"s"
1:"s"
2:"s"
3:"s"
In:
$meta = $stmt->result_metadata();
the value of $meta becomes:
current_field:0
field_count:13
lengths:null
num_rows:0
type:1
Meaning that no rows were selected from the database. I have executed this sql on the database manually and it returns rows. What is wrong with my code, that makes it return no rows from the database?
EDIT: I saw that this answer says to put the "ssss" at the start of the $params so I did that and got this error in the $stmt object:
errno:2031
error:"No data supplied for parameters in prepared statement"
error_list:array(1)
propertyNode.hasAttribute is not a function
I don't understand what ways you've tried, but I will try to answer:
according to bind_param manual:
first argument of bind_param is a string, like 'ssss'.
second and other arguments - are values to be inserted into a query.
So, your $a_params array should be not
0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:array(4)
0:"s"
1:"s"
2:"s"
3:"s"
But:
0:"ssss"
1:"New Zealand"
2:"Grey Lynn"
3:"Auckland"
4:"Auckland"
See? All values are strings. And placeholders' types are the first one.
Also take into consideration that order of arguments in $a_params must be the same as order of parameters in bind_param. This means that, i.e., $a_params like
0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:"ssss"
is wrong. Because first element of $a_params will be the first argument of bind_param and in this case it's not a "ssss" string.
So, this means that after you filled $a_params with values, placeholders' string should be added to the beginning of $a_params, with array_unshift for example:
// make $a_param_type a string
$str_param_type = implode('', $a_param_type);
// add this string as a first element of array
array_unshift($a_params, $str_param_type);
// try to call
call_user_func_array(array($stmt, 'bind_param'), $a_params);
In case this didn't work, you can refer to a part of answer you provided, where values of $a_params are passed by reference to another array $tmp, in your case you can try something like:
// make $a_param_type a string
$str_param_type = implode('', $a_param_type);
// add this string as a first element of array
array_unshift($a_params, $str_param_type);
$tmp = array();
foreach ($a_params as $key => $value) {
// each value of tmp is a reference to `$a_params` values
$tmp[$key] = &$a_params[$key];
}
// try to call, note - with $tmp, not with $a_params
call_user_func_array(array($stmt, 'bind_param'), $tmp);
REMEMBER: The second parameter of call_user_func_array needs to be referenced, not just a normal array. This is key. The accepted answer is good but just missing one thing, making the parameter referenced:
function makeValuesReferenced($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
$stmt = $conn->prepare($query);
//$stmt->bind_param($queryParamTypes, $queryParams);
call_user_func_array(array($stmt, 'bind_param'), makeValuesReferenced($queryParams));
$stmt->execute();
I have form with lot of inputs, and I'm trying to import them in database (mysql).
I want to use bind but trying to avoid writing all variables so many times. Probably I can't explain so good, so I will here is a code
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
if(!empty($firstName)&& !empty($lastName)) {
$unos = $db->prepare("INSERT INTO members (firstName,lastName,gender) VALUES (?,?,?)");
$unos->bind_param('sss', $firstName, $lastName, $gender);
if($unos->execute()) {....
1.Well this is working fine , and it's not a problem, but now I want to add more inputs so I tried this
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
$param=array('$firstName','$lastName','$gender');
$type='sss';
$param_list = implode(',', $param);
if(!empty($param)) {
$unos = $db->prepare("INSERT INTO members (firstName,lastName,gender) VALUES (?,?,?)");
$unos->bind_param($type,implode(',', $param));
if($unos->execute()) {....
and it's not working. I get "Number of elements in type definition string doesn't match number of bind variables"...
I don't get it, because when I echo this implode thing I get what I need.
I'm pretty newbie with PHP, so help will be so precious. :)
You can try this:
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
$param=array('firstName' => 's','lastName' => 's','gender' => 's');
if(!empty($param)) {
$unos = $db->prepare("INSERT INTO members (". implode(',', array_keys($param) .") VALUES (". implode(',', array_fill(0, count($param), '?')) .")");
foreach($param as $paramName => $paramType) {
$unos->bind_param($paramType, $paramName);
}
if($unos->execute()) {....
You can pus as many parameters to $param array. Key should the name of the db column, value is its type.
You will need a variable for each question mark, but you will also need to bind each of the parameters separately. In your current situation, you bind a comma-separated list of values as a single string parameter.
What about this? I attempted to make the entire code rely on a single array of fields. If you want extra fields, you can just add them to the array and the rest of the code should respond to it. I don't have a proper test environment at hand and I typed this code by heart, so sorry for any typos. :)
// The fixes list of allowed/expected fields. Other values are ignored.
$fields = array('firstname', 'lastname', 'gender');
// Check if each value exists, and put them in an array.
$paramvalues = array();
foreach ($fields as $field) do
{
if (!isset($_POST[$field]))
die("missing field $field");
$paramvalues[] = & $_POST[$field]; // Bind_param wants a ref value, hence `&`
}
// Build a list of fields for the dynamic query.
$fieldlist = implode($fields, ',');
// And a list of placeholders.
$paramlist = implode(array_fill(0, count($fields), '?'), ',');
// And a list of types, assuming all parameters are strings.
$paramtypes = str_pad('', count($fields), 's');
// Prepare the query
$unos = $db->prepare("INSERT INTO members ($fieldlist) VALUES ($paramlist)");
// Build an array of reference values to be passed to call_user_func_array:
$paramrefvalues = array();
$paramrefvalues[] = $paramtypes
foreach ($paramvalues as $value) do
{
$paramrefvalues[] = & $value;
}
// Call bind_param using this array of by-ref parameters
call_user_func_array(array($unos, 'bind_param'), $paramrefvalues);
This code is loosely based on this article
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
So, I have this PHP code:
$tabid = getTabid($module);
if($tabid==9)
$tabid="9,16";
$sql = "select * from field ";
$sql.= " where field.tabid in(?) and";
Now, how exactly does the ? work here? I vaguely understand that in PHP, ?: is a ternary operator, but the colon isn't being used here, and ? is part of a Postgresql query anyway.
The final query looks a bit like this:
select * from field where field.tabid in('9,16')
So, the question mark is replaced by the contents of $tabid, how does that happen?
The issue is that ('9,16') is not accepted by Postgres as an integer, it needs to be written like (9,16), so how do I do that? How do I remove the apostrophes?
Thanks a lot for the help, have a good day!
edit: More code was requested:
$sql.= " field.displaytype in (1,2,3) and field.presence in (0,2)";
followed by if statements, I think this is the relevant one:
if($tabid == 9 || $tabid==16)
{
$sql.= " and field.fieldname not in('notime','duration_minutes','duration_hours')";
}
$sql.= " group by field.fieldlabel order by block,sequence";
$params = array($tabid);
//Running the query.
$result = $adb->pquery($sql, $params);
Oh, I think I see now, I think it is a place holder, a part of the pquery function:
function pquery($sql, $params, $dieOnError=false, $msg='') {
Stuff
$sql = $this->convert2Sql($sql, $params);
}
Now, this is where it seems to get fun, here's part of the convert2Sql function:
function convert2Sql($ps, $vals) {
for($index = 0; $index < count($vals); $index++) {
if(is_string($vals[$index])) {
if($vals[$index] == '') {
$vals[$index] = "NULL";
}
else {
$vals[$index] = "'".$this->sql_escape_string($vals[$index]). "'";
}
}
}
$sql = preg_replace_callback("/('[^']*')|(\"[^\"]*\")|([?])/", array(new PreparedQMark2SqlValue($vals),"call"), $ps);
return $sql;
}
The problem I think lies in the
$vals[$index] = "'".$this->sql_escape_string($vals[$index]). "'"; line.
The sql_escape_string($str) function just returns pg_escape_string($str).
Sorry for the super long edit, but I still haven't been able to get past I am afraid, thanks for all the help!
Edit 2: I fixed the problem, all it took was changin $tabid = "9,16" to $tabid = array(9,16). I have no idea why, oh and I also had to remove the group by statement because Postgresql requires every field to be placed in that statement.
it is a positional parameter for a prepared statement
See: http://php.net/manual/en/function.pg-prepare.php
You don't actually 'remove' the quotes, you have to pass SQL array of ints instead of a string value into the parameter when doing pg_execute
An example:
// Assume that $values[] is an array containing the values you are interested in.
$values = array(1, 4, 5, 8);
// To select a variable number of arguments using pg_query() you can use:
$valuelist = implode(', ', $values);
// You may therefore assume that the following will work.
$query = 'SELECT * FROM table1 WHERE col1 IN ($1)';
$result = pg_query_params($query, array($valuelist))
or die(pg_last_error());
// Produces error message: 'ERROR: invalid input syntax for integer'
// It only works when a SINGLE value specified.
Instead you must use the following approach:
$valuelist = '{' . implode(', ', $values . '}'
$query = 'SELECT * FROM table1 WHERE col1 = ANY ($1)';
$result = pg_query_params($query, array($valuelist));