The code I've written so far works fine if there is only one named place holder for a prepared statement but if there are multiple conditions for a query, it doesn't return any results from the database.
For instance:
$query = array();
$query['columns'] = array('*');
$query['tables'] = array('esl_comments');
$query['where'] = array(
'esl_comments.commentVisible' => array('=', 'Y')
);
Works fine. But if I try:
$query = array();
$query['columns'] = array('*');
$query['tables'] = array('esl_comments');
$query['where'] = array(
'esl_comments.commentVisible' => array('=', 'Y'),
'esl_comments.commentID' => array('=', '1'),
);
(Note the additional commentID parameter) it fails to return anything despite there being data in the mySQL database that satisfies the conditions.
The PDO code i've written is:
$sql ='SELECT ';
foreach($query['columns'] as $column){ //What columnns do we want to fetch?
$sql.=$column . ", ";
}
$sql = rtrim($sql, " ,");
$sql .=' FROM '; //Which tables will we be accessing?
foreach($query['tables'] as $tables){
$sql.=$tables . ", ";
}
$sql = rtrim($sql, " ,"); //Get rid of the last comma
$sql .=' WHERE ';
if(array_key_exists('where', $query)) //check if a where clause was provided
{
$fieldnames = array_keys($query['where']);
$count = 0;
$size = sizeof($fieldnames);
$bindings = array();
foreach($query['where'] as $where){
$cleanPlaceholder = str_replace("_", "", $fieldnames[$count]);
$cleanPlaceholder = str_replace(".", "", $cleanPlaceholder);
$sql.=$fieldnames[$count].$where[0].":".$cleanPlaceholder." AND ";
$bindings[$cleanPlaceholder]=$where[1];
$count++;
}
$sql = substr($sql, 0, -5); //Remove the last AND
}
else{ //no where clause so set it to an always true check
$sql.='1=1';
$bindings=array('1'=>'1'); //Provide default bindings for the statement
}
$sql .= ';'; //Add the semi-colon to note the end of the query
echo $sql . "<br/><br/>";
// exit();
$stmt = $this->_connection->prepare($sql);
foreach($bindings as $placeholder=>$bound){
echo $placeholder . " - " . $bound."<br/>";
$stmt->bindParam($placeholder, $bound);
}
$result = $stmt->execute();
echo $stmt->rowCount() . " records<br/>";
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
I'm building queries dynamically and therefore I am cleaning the placeholders, by stripping them of periods and underscores - hence the use of the 'cleanPlaceholder' variable.
The query being generated looks like this:
SELECT * FROM esl_comments WHERE esl_comments.commentVisible=:eslcommentscommentVisible AND esl_comments.commentID=:eslcommentscommentID;
And the parameters being bound look like this:
eslcommentscommentVisible - Y
eslcommentscommentID - 1
bindParam Requires a reference
The problem is caused by the way you bind parameters in the foreach loop.
foreach($bindings as $placeholder=>$bound){
echo $placeholder . " - " . $bound."<br/>";
$stmt->bindParam($placeholder, $bound);
}
bindParam requires a reference. It binds the variable, not the value, to the statement. Since the variable in a foreach loop is reset at the start of each iteration, only the last reference to $bound is left intact, and you end up binding all your placeholders to it.
That's why your code works when $query['where'] contains only one entry, but fails when it contains more than one.
You can solve the problem in 2 ways:
Pass by reference
foreach($bindings as $placeholder => &$bound) { //pass $bound as a reference (&)
$stmt->bindParam($placeholder, $bound); // bind the variable to the statement
}
Pass by value
Use bindValue instead of bindParam:
foreach($bindings as $placeholder => $bound) {
$stmt->bindValue($placeholder, $bound); // bind the value to the statement
}
Related
Im a new and just learning php. I have a data table with search boxes with this code.
$condition = '';
if(isset($_REQUEST['username']) and $_REQUEST['username']!="") {
$condition .= ' AND username LIKE "%'.$_REQUEST['username'].'%" ';
}
if(isset($_REQUEST['useremail']) and $_REQUEST['useremail']!=""){
$condition .= ' AND useremail LIKE "%'.$_REQUEST['useremail'].'%" ';
}
What I need is to search with both username AND useremail. I have attempted everything I know and spent a few hours searching for a solution but with no success.
You could write a complicated set of IF's with equals and not equals tests all over the place, but as the list of test gets bigger the IF's get almost impossible to maintain or understand. So it might be simpler to just build and array of things to AND in the query
$condition = '';
$and = []; #init the array
if(isset($_REQUEST['username']) and $_REQUEST['username']!="") {
$and[] = ['name' => 'username', 'value' => $_REQUEST['username'] ];
}
if(isset($_REQUEST['useremail']) and $_REQUEST['useremail']!=""){
$and[] = [''name' => 'useremail', 'value' => $_REQUEST['useremail'] ];
}
#now build the condition string
foreach ($and as $i => $andMe) {
if ( $i != 0 ){
// AND required here
$condition .= ' AND ';
}
$condition .= $andMe['name'] . ' = ' . $andMe['value'];
}
Also I have replaced the LIKE with an = as it seems more appropriate, I assume you dont ask people to enter something a bit like there user name and email, but in fact ask for the actual username or email
Of course that would still be susceptible to SQL Injection Attack So a better solution would be
#now build the condition string
foreach ($and as $i => $andMe) {
if ( $i != 0 ){
// AND required here
$condition .= ' AND ';
}
$condition .= $andMe['name'] . ' = ?';
}
And then prepare the query and use the value part to bind to the parameters.
Issue is you have leading AND in your query.
push condition to array then join conditions.
like that
$condition_array = [];
if(isset($_REQUEST['username']) and $_REQUEST['username']!="") {
$condition_array[] = 'username LIKE "%'.$_REQUEST['username'].'%" ';
}
if(isset($_REQUEST['useremail']) and $_REQUEST['useremail']!=""){
$condition_array[] = 'useremail LIKE "%'.$_REQUEST['useremail'].'%" ';
}
$condition = implode(" AND ",$condition_array);
You can create an array of all the keys that are to be searched. Then, create a new array and collect all conditions. Implode them in the end with AND as the glue. This way, query is made correctly without needing to add 100 different if conditions.
Use PDO objects to avoid SQL injection attacks. In the below snippet, if you ever need to add 1 more column for search, just add it in the below $keys array and rest works as usual without needing any further refactoring.
Snippet:
<?php
$keys = ['username', 'useremail'];
$conditions = [];
$placeholders = [];
foreach($keys as $key){
if(!empty($_REQUEST[ $key ])){
$conditions = " $key LIKE ?";
$placeholders[] = '%' . $_REQUEST[ $key ] . '%';
}
}
// you need to create $mysqli object here
if(count($conditions) === 0){
$stmt = $mysqli->prepare('select * from table');
$stmt->execute();
// rest of your code
}else{
$stmt = $mysqli->prepare('select * from table where '. implode(" AND ", $conditions));
$stmt->bind_param(str_repeat('s', count($placeholders)), ...$placeholders);
$stmt->execute();
// rest of your code
}
I am trying to construct and execute a delete query when the table_name and the conditional_clauses are passed as parameters in OOP fashion. I am using PDO by wrapping it in a custom wrapper. I am using prepared statements, with named placeholders. In every case, I am passing an associative array inside PDO->execute() function, where the array_keys are the name of the placeholders used and the array_value are the corresponding values to be substituted. I am facing issues only in one case when I want to specify an IS NULL condition with WHERE clause.
Basically, if I want to search for something like:
SELECT * FROM EMPLOYEES WHERE salary > 10000 AND skill IS NULL
I am able to dynamically construct a prepared statement which looks like:
$sql = SELECT * FROM employees WHERE salary > :salary AND skill IS :skill
And then execute the prepared SQL as:
$stmt->execute(["salary" => 10000,
"skill" => null])
This is where I am facing the issue. I am getting a fatal error here only when a value is null. And, I want to include checking for IS NULL functionality in my wrapper.
Please note -
I want to achieve the purpose without using bindValue() or
bindParam() functions.
I have turned emulation off (as MySQL can sort all placeholders out
properly).
Using ? as placeholders isn't an option for me. I'll have to
re-design my entire codebase otherwise.
Here's the code snippet for reference:
<?php
class DeleteQuery {
protected function where(array $whereCondition, array &$values): string{
$whereClause = ' WHERE ';
$i = 0;
$j = 0;
$hasComparators = array_key_exists("comparators", $whereCondition);
$hasConjunctions = array_key_exists("conjunctions", $whereCondition);
$comparatorCount = $hasComparators ? count($whereCondition["comparators"]) : 0;
$conjunctionCount = $hasConjunctions ? count($whereCondition["conjunctions"]) : 0;
foreach ($whereCondition["predicates"] as $predicate_key => &$predicate_value) {
$whereClause .= $predicate_key;
$whereClause .= ($hasComparators and ($i < $comparatorCount)) ?
' ' . $whereCondition["comparators"][$i++] . ' ' : ' = ';
if (is_array($predicate_value)) {
$whereClause .= "('" . implode("', '", $predicate_value) . "')";
unset($whereCondition['predicates'][$predicate_key]);
} else {
$whereClause .= ':' . $predicate_key;
}
$whereClause .= !($hasConjunctions and ($j < $conjunctionCount)) ?
'' : ' ' . $whereCondition["conjunctions"][$j++] . ' ';
}
$values = array_merge($values, $whereCondition['predicates']);
return $whereClause;
}
public function delete($tblName, $conditions) {
$sql = "DELETE FROM " . $tblName;
$values = [];
if (!empty($conditions) && is_array($conditions)) {
/* If the stmt has WHERE clause */
if (array_key_exists("where", $conditions)) {
$sql .= $this->where($conditions['where'], $values);
}
/* If the stmt has ORDER BY clause */
if (array_key_exists("order_by", $conditions)) {
$sql .= $this->order_by($conditions['order_by']);
}
/* If the stmt has LIMIT clause */
if (array_key_exists("limit", $conditions)) {
$sql .= $this->limit($conditions['limit'], $values);
}
}
echo $sql . PHP_EOL;
print_r($values);
}
}
$deleteConditions = [
"where" => array(
"predicates" => ["skill" => null],
"comparators" => ["IS"],
),
/* other conditional clauses */
];
$obj = new DeleteQuery();
$obj->delete("employees", $deleteConditions);
The IS operator can't be used with an expression. IS NULL and IS NOT NULL are keywords.
You need a test that works with both null and non-null values of :skill. You can use the null-safe equality operator, <=>
$sql = 'SELECT *
FROM employees
WHERE salary > :salary
AND skill <=> :skill';
I have an SQL $query like this;
UPDATE `tags` SET `tags.name` = :name,
`tags.slug` = :slug,
`tags.date_published` = :date_published,
`tags.meta_title` = :meta_title,
`tags.meta_description` = :meta_description,
`tags.meta_keywords` = :meta_keywords
WHERE `tags.id` = :id
And my $values array;
Array
(
[:name] => iphone
[:slug] => iphone
[:date_published] => 2016-03-27
[:meta_title] => iphone Yazıları
[:meta_description] => iphone hakkında son gelişmeler ve faydalı bilgiler.
[:meta_keywords] => iphone yazıları, iphone hakkında bilgiler, iphone haberleri
[:id] => 24
)
I'm trying to update one row with PDO's prepare function;
$sth = $this->db->prepare($query);
$sth->execute($values);
echo "affected rows: ".$sth->rowCount(); // prints 0
return $sth->rowCount() ? true : false;
And I get no errors but rows are not affected after execute the query. Where is my mistake?
mysql tags table
edit:
I'm creating values array like this;
$params = array(
"id", "name", "slug", "date_published",
"meta_title", "meta_description", "meta_keywords");
$values = array();
foreach ($params as $key) {
#$values[$key] = $_POST[$key];
}
And this is how I create the query;
$query = "UPDATE `$table` SET";
$values = array();
/* Add field names and placeholders to query. */
foreach ($params as $key => $value) {
$query .= " `{$table}.{$key}` = :" . $key . ",";
$values[":" . $key] = $value;
}
/* Remove last comma. */
$query = substr($query, 0, -1);
/* Build where condition. */
if ($cond) {
$query .= " WHERE ";
foreach ($cond as $key => $value) {
$query .= " `{$table}.{$key}` = :" . $key . ",";
$values[":" . $key] = $value;
}
/* Remove last comma. */
$query = substr($query, 0, -1);
}
You're escaping the column names incorrectly. For example, the back ticks should not enclose the whole of tags.name, but rather the table and column names separately:
`tags`.`name`
Checking the output of PDO::errorInfo() confirms this (emulated prepares must be set to false in order to see this, however).
General notes:
You don't need to specify the colon prefix for the keys of the array being bound (likewise when explicitly binding columns via bindParam() or bindValue()).
You should avoid using back ticks in your PDO queries. They're a MySQL-specific feature, and PDO is not a SQL abstraction layer (thereby preventing one of the major benefits of PDO - portability).
I'm trying to build a custom class to manage operations with database. I'm a beginner with OOP so if you have any suggests please tell me. By the way, i have an update method which took as parameter the name of the table, the fields to update, the values with which i want to update the fields and the fields and values to put in where clause of query.
At this time, i have two distinct arrays, one for the set part and one for the where part.
I build the query string like so PDOStatement Object ( [queryString] => UPDATE Ordini SET Nome=':Nome', Cognome=': Cognome', Telefono=': Telefono' WHERE ID=':ID' )
Now i would like to bind the params to the variables and execute the query and that's the problem. I try this way but the query don't update the fields.
- In $values i have the values i want to bind to variables in the SET part
- In $wv i have the values i want to bind to variables in the WHERE part
- In $fieldsQuery i have the placeholders for the SET part(":Nome" for example)
- In $fieldsWhere i have the placeholders for the WHERE part
How can i bind in the right way the placeholders with the variables?
public function update($table=NULL, $fieldsQuery=NULL, $fieldsValues = NULL, $whereFields=NULL, $whereValues=NULL, $whereOperators = NULL)
{
if($fieldsQuery != NULL)
$fields = explode(",", $fieldsQuery);
if($fieldsValues != NULL)
$values = explode(",", $fieldsValues);
if($whereFields != NULL)
$wf = explode(",", $whereFields);
if($whereValues != NULL)
$wv = explode(",", $whereValues);
$fieldsQuery = array_map(function($field) { return ":$field";}, $fields);
$bindValuesSet = array_combine($fieldsQuery, $values);
//return an array in which every field is => Fieldname=':Fieldname'
$bindSetInitial = array_combine($fields, $fieldsQuery);
//transform every item in array from field to :field
$fieldsWhere = array_map(function($field) { return ":$field";}, $wf);
$bindValuesWhere = array_combine($fieldsWhere, $wv);
$bindWhereInitial = array_combine($wf, $fieldsWhere);
//implode an array mantaining both key and value
$fieldsValues = implode(', ', array_map(function ($v, $k) { return sprintf("%s='%s'", $k, $v); }, $bindSetInitial, array_keys($bindSetInitial)));
$fieldsWhere = implode(', ', array_map(function ($v, $k) { return sprintf("%s='%s'", $k, $v); }, $bindWhereInitial, array_keys($bindWhereInitial)));
$query = $this->db->prepare('UPDATE ' . $table . ' SET ' . $fieldsValues . ' WHERE ' . $fieldsWhere);
$query->bindParam(':Nome', $values[0]);
$query->bindParam(':Cognome', $values[1]);
$query->bindParam(':Telefono', $values[2]);
$query->bindParam(':ID', $wv[0], PDO::PARAM_INT);
$query->execute();
print_r($query->debugDumpParams());
}
':Nome' is not a placeholder for a prepared statement. It's just a string ':Nome'
Placeholder is :Nome (without `) and without any spaces, tabs etc.
I.e. : Nome is not a placeholder too.
So, you query should be:
UPDATE Ordini SET Nome=:Nome, Cognome=:Cognome, Telefono=:Telefono WHERE ID=:ID
And thanks to #Fred-ii- - read error handling section of PDO
Generating the statement elements
I am trying to create an MSSQL prepared statement in a generic way.
Basically, I loop trough the fields, and add them to the prepared SQL string and to the reference parameter (scroll down to see the code).
This results in (names removed):
Prepared query: :
INSERT INTO table
([field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
and
sPrepStatement:
return array(&$aLine[0],&$aLine[1],&$aLine[2],&$aLine[3],&$aLine[4],&$aLine[5],&$aLine[6],&$aLine[7],&$aLine[8],&$aLine[9],&$aLine[10],&$aLine[11],&$aLine[12],&$aLine[13],&$aLine[14]);
Preparing the statement
I've tried the following 4 approaches to get this to work with the sqlsrv_prepare statement:
$oSQLStmnt = sqlsrv_prepare($dbhandle, $sPreppedSQL, eval($sPrepStatement));
$fReturnPrepVals = function(){ return eval($sPrepStatement); } ;
$oSQLStmnt = sqlsrv_prepare($dbhandle, $sPreppedSQL, $fReturnPrepVals);
$oSQLStmnt = sqlsrv_prepare($dbhandle, $sPreppedSQL,$aPrepValues);
$oSQLStmnt = call_user_func_array('sqlsrv_prepare',array($dbhandle,$sPreppedSQL, eval($sPrepStatement)));
This either does not work, or inserts blanks into the database.
This is the loop that executes the SQL:
foreach ($aLines as $iLineNum => $sLine) {
$aLine = explode('|', $sLine);
print_r($aPrepValues);
print_r(eval($sPrepStatement));
sqlsrv_execute($oSQLStmnt);
}
The eval($sPrepStatement) works just fine, which makes sense. But I assume it is 'parsed too early' to work in the prepare statement. It should actually only be parsed as the query is executed, but I'm at a loss how to achieve that.
Code for the generation of sql statements:
<?php
//Setup the basic query
$sSql = "INSERT INTO " . $sQName . "\n";
$sFieldNames = "(";
$sValues = "VALUES(";
//This will be evalled to prepare the query, so inserting will be easy.
$sPrepStatement = "return array(";
//FieldIndex to keep track of the current column number
$iFI = 0;
$aLine = array();
foreach ($aSQLQueries[$sQName]['fields'] as $sFieldName => $sQ) {
$sFieldNames .= "[" . $sFieldName . "],";
$sValues .= "?,";
//Init the $aLine var to prevent errors
$aLine[$iFI] = '';
//This will be evalled, so no "" as that would parse it directly
//The values wil be passed by reference
$sPrepStatement .= '&$aLine[' . $iFI . '],';
//Different approach
$aPrepValues[] = &$aLine[$iFI];
$iFI++;
}
$sFieldNames = substr($sFieldNames, 0, -1) . ")";
$sValues = substr($sValues, 0, -1) . ")";
$sPrepStatement = substr($sPrepStatement, 0, -1) . ");";
$sPreppedSQL = $sSql . $sFieldNames . " " . $sValues;
I ended up making it work with PDO, only to find that it could not handle millions of imports at a time, so I eventually used CSV import