Structuring my MySQL search query for use with PDO - php

I'm attempting to write a parameterized query with PDO that accepts a number of inputs and acts as a search on a specific table.
There are a number of columns I wish to search on but each of which could be optional. The query simplified might look like:
SELECT * FROM item
WHERE name LIKE '%?%'
AND col2 IN (?, ?, .......)
AND col3 IN (?, ?, .......)
...
and with the IN clause, there could be any number (0 or more) of terms for each column.
Since the IN clause won't work with 0 values, and in PDO I'd have to iterate over each array passed in for each IN clause - I'm wondering if there's a better way to structure this as it seems like a big mess.

You can use "call_user_func_array" to make it dynamic. This is what I use:
public function selectMSData($handlename, $sql, $type='', $params = array())
{
if ( ! is_string($sql) || ! is_array($params) ) {
die('wrong param types');
}
$this->dbconnect($handlename); //connect to db and save connection with handle-name
$result = array();
$aRows = 0;
if(sizeof($params)==0) {
//simple query without runtime parameters
$msres = $this->dbhandle[$handlename]->query($sql);
if($msres === false) {
//log error
} else {
while($mres = $msres->fetch_array(MYSQLI_ASSOC)) {
$aRows++;
$result[] = $mres;
}
}
} else {
//prepared statement using runtime parameters
$stmt = $this->dbhandle[$handlename]->prepare($sql);
if(!$stmt) {
//log error
}
$valArr = array();
$valArr[] = $type;
foreach($params as $pkey => $pval) {
$valArr[] = &$params[$pkey];
}
call_user_func_array(array(&$stmt, 'bind_param'), $valArr);
if(!$stmt->execute()) {
//log error
};
$stmt->store_result(); //fetch is super-slow for text-fields if you don't buffer the result!!
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$resfields[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $resfields);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$result[] = $c;
$aRows++;
}
$stmt->close();
}
$this->result = $result;
return $aRows;
}
and you call it like this:
$db->selectMSData('my_db_name', 'SELECT * FROM example WHERE a=? AND b=? LIMIT 1', 'ss', array($a, $b));

Related

PDO update multiple rows in a single function [duplicate]

I'm working with PHP PDO and I have the following problem:
Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in /var/www/site/classes/enterprise.php on line 63
Here is my code:
public function getCompaniesByCity(City $city, $options = null) {
$database = Connection::getConnection();
if(empty($options)) {
$statement = $database->prepare("SELECT * FROM `empresas` WHERE `empresas`.`cidades_codigo` = ?");
$statement->bindValue(1, $city->getId());
}
else {
$sql = "SELECT * FROM `empresas`
INNER JOIN `prods_empresas` ON `prods_empresas`.`empresas_codigo` = `empresas`.`codigo` WHERE ";
foreach($options as $option) {
$sql .= '`prods_empresas`.`produtos_codigo` = ? OR ';
}
$sql = substr($sql, 0, -4);
$sql .= ' AND `empresas`.`cidades_codigo` = ?';
$statement = $database->prepare($sql);
echo $sql;
foreach($options as $i => $option) {
$statement->bindValue($i + 1, $option->getId());
}
$statement->bindValue(count($options), $city->getId());
}
$statement->execute();
$objects = $statement->fetchAll(PDO::FETCH_OBJ);
$companies = array();
if(!empty($objects)) {
foreach($objects as $object) {
$data = array(
'id' => $object->codigo,
'name' => $object->nome,
'link' => $object->link,
'email' => $object->email,
'details' => $object->detalhes,
'logo' => $object->logo
);
$enterprise = new Enterprise($data);
array_push($companies, $enterprise);
}
return $companies;
}
}
It looks like you're trying to build a long(?) series of 'or' comparisons: if (x=1) or (x=2) or (x=3) etc.... You may find it easier to replace it with:
$cnt = count($options);
if ($cnt > 0) {
$placeholders = str_repeat(', ?', $cnt - 1);
$sql .= 'WHERE '`prods_empresas`.`produtos_codigo` IN (?' . $placeholders . ')';
}
which, if there were 5 options, would give you
WHERE prods_empresas.produtos_condigo IN (?, ?, ?, ?, ?)
And then do the values binding with:
$pos = 1;
foreach ($options as $option) {
$statement->bindValue($pos, $option->getId());
$pos++
}
You have a mismatch between the amount of bound parameters and the amount of binds in the SQL. Double check that the amount of ? and the amount of bound parameters is the same.
Additionally, HY093 will show up if you have tried to bind a parameter that does not exist:
$stmt = "INSERT INTO table VALUES (:some_value)";
$stmt->bindValue(':someValue', $someValue, PDO::PARAM_STR);
See that :some_value does not match :someValue! The fix is:
$stmt = "INSERT INTO table VALUES (:some_value)";
$stmt->bindValue(':some_value', $someValue, PDO::PARAM_STR);
Positional parameters in SQL start at 1. You're handling this by binding to position $i+1 in your $options loop.
But then you bind the last parameter for cidades_codigo to position count($options), which overwrites the last parameter set in the $options loop.
You need to bind the last parameter to position count($options)+1.
FWIW, you don't need to bindValue() at all. It's easier to just pass an array of parameters to execute(). Here's how I'd write this function:
public function getCompaniesByCity(City $city, $options = null) {
$database = Connection::getConnection();
$sql = "SELECT * FROM `empresas` WHERE `empresas`.`cidades_codigo` = ?"
$params = array();
$params[] = $city->getId();
if ($options) {
$sql .= " AND `prods_empresas`.`produtos_codigo` IN ("
. join(",", array_fill(1, count($options), "?") . ")";
foreach ((array)$options as $option) {
$params[] = $option->getId();
}
}
$statement = $database->prepare($sql);
echo $sql;
$statement->execute($params);
. . .
Also be sure to check the return value of prepare() and execute(), it will be false if there's an error, and you need to check for that and report the error. Or else enable PDO to throw exceptions on error.
I was running into this problem due to having extra entries in the named parameter mapping array passed to PDO::Statement->execute()
$args=array (":x" => 17 );
$pdo->prepare("insert into foo (x) values (:x)");
$pdo->execute($args); // success
$args[':irrelevant']=23;
$pdo->execute($args) // throws exception with HY093
Since you have made $i+1 in the loop so count($options) would equal the last $i+1 which makes a duplicate binding.Try
foreach($options as $i => $option)
{
$statement->bindValue($i + 1, $option->getId());
}
$statement->bindValue(count($options)+1, $city->getId());

How to make the preparation more efficient in database query using PDO?

For example, I have a couple of tables in my database, e.g., user, product, etc. Fro every table, I have at least an associated class with a couple of methods, such as addUser, updateUserName, updateUserPassword, etc. For every method, I need to prepare the SQL when using PDO, which looks like this:
$sql = "INSERT INTO `user`
(`id`,`username`,`password`,`log`)
VALUES
(:id, :username, :password, :log)";
Then I store the values in an array like this:
$array = array('id'=>$id, 'username'=>$username, 'password'=>$password, 'log'=>$log);
Then I use the PDO thing:
$pdo = new PDO($dsn, $user, $password);
$mysql = $pdo->prepare($sql);
$mysql->execute($array);
So it seems that for all different methods inside the User class, I need to do this "prepare" thing. Isn't it too tedious? Is there a more efficient way to do so, especially the part where I store the values in an array considering there exist a table with many columns in which case I would end up with a very long prepare sentence?
Since Your own is insert and update try these
//to query the database with prepared statements
public function query ($sql, $parameters = array()) {
//setting error to false to prevent interferance from previous failed queries
$this->_error = false;
//prepare SQL statement
if ($this->_query = $this->_pdo->prepare ($sql)) {
//checking to see whether any parameters were submitted along
if (count($parameters)) {
//setting the initial position for the binding values
$position = 1;
//getting the individual parameters and binding them with their respective fields
foreach ($parameters as $param) {
$this->_query->bindValue ($position, $param);
$position++;
}
}
}
//executing the sql
if ($this->_query->execute()) {
//getting the number of rows returned
$this->_count = $this->_query->rowCount();
//keeping the results returned
$this->_results = $this->_query->fetchAll (PDO::FETCH_OBJ);
} else {
$this->_error = true;
}
//returning all values of $this
return $this;
}
//to insert data into a prescribed table
public function insert ($table, $parameters = array()) {
//checking if the $fields are not empty
if (count($parameters)) {
//making the keys of the array fields
$fields = array_keys ($parameters);
//creating the to-bind-values in the form (?, ?, ...)
$values = '';
$x = 1;
foreach ($parameters as $field => $value) {
//$value is different from $values
$values .= '?';
if ($x < count($parameters)) {
$values .= ', ';
$x++;
}
}
//generating $sql
$sql = "INSERT INTO `{$table}` (`".implode ('`, `', $fields)."`) VALUES ({$values})";
//executing the sql
if (!$this->query($sql, $parameters)->error()) {
return true;
}
}
return false;
}
//to update data in a prescribed table
public function update ($table, $id = null, $parameters = array()) {
//checking that $parameters is not an empty array
if (count($parameters)) {
$set = '';
$x = 1;
foreach ($parameters as $field => $value) {
$set .= "`{$field}` = ?";
if ($x < count($parameters)) {
$set .= ', ';
$x++;
}
}
if ($id) {
//generating query
$sql = "UPDATE `{$table}` SET {$set} WHERE `id` = {$id}";
} else {
$sql = "UPDATE `{$table}` SET {$set} WHERE 1";
}
//executing the query
if (!$this->query($sql, $parameters)->error()) {
return true;
}
}
return false;
}

how to bind in mysqli dynamically

I'm in the middle of creating a custom Database Class to suit the requirements of the company i'm developing for. I currently have this:
class DBC {
protected $Link;
protected $Results;
public function __construct($Host = null,$User = null ,$Pass = null,$Database = null){
if ($Host === null OR $User === null OR $Pass === null OR $Database === null){
trigger_error("Incorrect Parameters Passed In The Database Link", E_USER_WARNING);
}
if (is_string($Host) AND is_string($User) AND is_string($Pass) AND is_string($Database)){
$this->Link = new mysqli($Host,$User,$Pass,$Database);
}else{
trigger_error("Expecting String(s), Array passed in one or more connection parameters",E_USER_ERROR);
}
}
public function Query ($Query,$Params){
$Query = $this->Link->prepare($Query);
$Query->bind_param();
}
}
Now.. I'm having a problem with how to sucessfully bind the parameters to the prepared statement.. For example, A Query will be submitted with this:
$DB = new DBC("Host","User","pass","database");
$DB->Query("SELECT * FROM Test WHERE Col=?",array("SearchCriteria"));
I've hit a block with figuring out how to bind_param and bind_result based on the results. A more clear insight is the normal procedure of MySQLi:
$SearchCriteria = "String";
$Query = $Database->prepare("SELECT * FROM Test WHERE Col=?");
$Query->bind_param('s',$SearchCriteria);
$Query->execute();
$Query->bind_results(/* Variables to match the column set */);
$Query->fetch();
$Query->close();
How can I bind the results and params to the prepared statement?
Below are copies of functions I use in a class that extends the mysqli class which do what you are asking.
function bind_placeholder_vars(&$stmt,$params,$debug=0) {
// Credit to: Dave Morgan
// Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
if ($params != null) {
$types = ''; //initial sting with types
foreach ($params as $param) { //for each element, determine type and add
if (is_int($param)) {
$types .= 'i'; //integer
} elseif (is_float($param)) {
$types .= 'd'; //double
} elseif (is_string($param)) {
$types .= 's'; //string
} else {
$types .= 'b'; //blob and unknown
}
}
$bind_names = array();
$bind_names[] = $types; //first param needed is the type string
// eg: 'issss'
for ($i=0; $i<count($params);$i++) { //go through incoming params and added em to array
$bind_name = 'bind' . $i; //give them an arbitrary name
$$bind_name = $params[$i]; //add the parameter to the variable variable
$bind_names[] = &$$bind_name; //now associate the variable as an element in an array
}
if ($debug) {
echo "\$bind_names:<br />\n";
var_dump($bind_names);
echo "<br />\n";
}
//error_log("better_mysqli has params ".print_r($bind_names, 1));
//call the function bind_param with dynamic params
call_user_func_array(array($stmt,'bind_param'),$bind_names);
return true;
}else{
return false;
}
}
function bind_result_array($stmt, &$row) {
// Credit to: Dave Morgan
// Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $params);
return true;
}
However, it sounds like you are doing something similar to what I have already done and have been using in many projects for a while now. Copy the contents of this pastebin (better_mysqli.php) into a new file and name it 'better_mysqli.php'
Then use it in your php program like so:
// include the class
include_once('better_mysqli.php');
// instantiate the object and open the database connection
$mysqli = new better_mysqli('yourserver.com', 'username', 'password', 'db_name');
if (mysqli_connect_errno()) {
die("Can't connect to MySQL Server. Errorcode: %s\n", mysqli_connect_error()), 'error');
}
// do a select query
$sth = $mysqli->select('select somecol, othercol from sometable where col1=? and col2=?', $row, array('col1_placeholder_value', 'col2_placeholder_value'));
while ($sth->fetch()) {
echo "somecol: ". $row['somecol'] ."<br />\n";
echo "othercol: ". $row['othercol'] ."<br />\n";
}
// the nice thing about this class is that the statement is only prepared once so if you use it again the already prepared statement is automatically used:
// do another select query with different placeholder values
$sth = $mysqli->select('select somecol, othercol from sometable where col1=? and col2=?', $row, array('other_col1_placeholder_value', 'other_col2_placeholder_value'));
while ($sth->fetch()) {
echo "somecol: ". $row['somecol'] ."<br />\n";
echo "othercol: ". $row['othercol'] ."<br />\n";
}
// the class supports the following methods: select, update, insert, and delete
// example delete:
$mysqli->delete('delete from sometable where col1=?', array('placeholder_val1'));

mysqli get_result alternative with multiple parameters

SELECT * FROM `ware_house_indents` WHERE `has_cancel` = ? AND `eid`= ?
i need to get result above query in mysqli. but i cant use get_result() function because it is not working in server.i have found this below function and it is working fine.but that function not possible to pass multiple parameters.please help me to solve this problem.
function db_bind_array($stmt, &$row)
{
$md = $stmt->result_metadata();
$params = array();
while($field = $md->fetch_field()) {
$params[] = &$row[$field->name];
}
return call_user_func_array(array($stmt, 'bind_result'), $params);
}
function db_query($db, $query, $types, $params)
{
$stmt = $db->prepare($query);
$bindRet = call_user_func_array(array($stmt,'bind_param'),
array_merge(array($types), $params));
$stmt->execute();
$result = array();
if (db_bind_array($stmt, $result) !== FALSE) {
return array($stmt, $result);
}
$stmt->close();
return FALSE;
}
if (($qryRes = db_query($mysqli, $sql, 'i', array(&$var))) !== FALSE) {
$stmt = $qryRes[0];
$row = $qryRes[1];
while ($stmt->fetch()) {
print_r($row);
}
$stmt->close();
}
Let me suggest you a quite indirect solution.
You'd do yourself a huge favor if start using PDO instead of mysqli
It has no mysqli disadvantages when dealing with prepared statements as it has a way better implementation of above functions out of the box. So, it will let you to get your data in a few lines:
$sql = "SELECT * FROM ware_house_indents WHERE has_cancel = ? AND eid= ?";
$stm = $pdo->prepare($sql);
$stm->execute(array($cancel,$eid));
$data = $stm->fetchAll(); // or fetch() if you need only one row
that's all

Dynamic Database function for getting and setting data

I have created a class to handle all database operations for my application. So far I have gathered and displayed the data in foreach statements within my view. What I wish to be able to do is get and set individual elements from a function from my database class.
Here is my database function for displaying data within a foreach:
public function test($sql, $type, $param)
{
$variables = array();
$results = array();
// Mysqli
if($stmt = $this->AC->Mysqli->prepare($sql))
{
$params = array_merge($type, $param);
call_user_func_array(array($stmt, 'bind_param'), $this->make_values_referenced($params));
$stmt->execute();
$stmt->store_result();
// Existance
if($stmt->num_rows > 0)
{
$meta = $stmt->result_metadata();
while($field = $meta->fetch_field())
{
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while($stmt->fetch())
{
$elemet = array();
foreach($row as $key => $val)
{
$element[$key] = $val;
}
$results[] = $element;
}
return $results;
}
else
{
$results = FALSE;
return $results;
}
}
else
{
die("ERROR: We could not connect.");
}
}
The function below is called from my model:
public function profile_information($account_name)
{
// Mysqli Variables
$sql = "SELECT $this->account_details.bio
FROM account_details
WHERE $this->account_details.account_name = ? LIMIT 10";
$type = array("s");
$param = array($account_name);
$results = $this->AC->Database->prepared_select_loop($sql, $type, $param);
$this->AC->Template->set_data('profile_information', $results, FALSE);
}
Once set within my model, I call the function within my controller and access it within my view with a foreach for displaying data:
$profile_information = $this->get_data('profile_information');
foreach ($profile_information as $row) :
//Displaying data here
endforeach;
The above works fine for displaying a large amount of data, but what I'm wanting to do is call the a database function that will allow me set individual data elements. Therefore not having to use a foreach if im only getting a limited amount of data (i.e. one row of Name, age, address)
A non dynamic way I have tackled this problem is to write the database for every function that only desires one row from the database:
function name($variable)
{
$sql = 'statement here';
$stmt = $this->AC->Mysqli->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result(bind results);
$stmt->fetch();
$this->AC->Template->set_data('id', $id);
$this->AC->Template->set_data('account_name', $account_name);
}
So in basically I want to make the above statement refactored into my database class and thus making it more dynamic.
I dont know how I would be able to tackle this problem, I don't want to use PDO, as I wish to find a solution within Mysqli. Any help would be appreciated.
Would you recommend just switching over to PDO?
Definitely.
Whole your function test() could be rewritten into three lines (assuming $param contains an array with parameters for the prepared statement):
public function test($sql, $param)
{
$stmt = $this->AC->pdo->prepare($sql);
$stmt->execute($param);
return $stmt->fetchAll();
}

Categories