PHP and PDO dynamic parameter binding - php

I'm trying to fix this butchered bit of code - as you might have guessed, I'm cocking up the bind param syntax. In fact, I'm not even sure what I'm trying to do is even possible. Here's the class method...
/***
*
* #select values from table
*
* #access public
*
* #param string $table The name of the table
*
* #param array $fieldlist Fields to return in results, defaults null
*
* #param array $criteria Search criteria by keyed by fieldname
*
* #param int $limit Limit of records to return, defaults 10
*
* #return Array on success or throw PDOException on failure
*
*/
public function dbSearch($table, $fieldList = null, $criteria = null, $limit = 10)
{
// setup $this->db to point to a PDO instance
$this->conn();
// build fieldlist
if( is_null($fieldList) OR !is_array($fieldList) OR count($fieldList) == 0) {
$returnFields = '*';
} else {
$returnFields = "'".implode("', '", $fieldList)."'";
}
// build criteria
if( is_null($criteria) OR !is_array($criteria) OR count($criteria) == 0) {
$whereClause = '';
} else {
$whereClause = array();
foreach ($criteria as $key => $value){
$bind_name = 'bind_'.$key; //generate a name for bind1, bind2, bind3...
$$bind_name = $value; //create a variable with this name with value in it
$bind_names[] = & $$bind_name; //put a link to this variable in array
$whereClause[] = "'$key' = :$bind_name";
}
$whereClause = count($whereClause) > 0 ? ' WHERE '.implode( ' AND ' , $whereClause ) : '';
}
$sql = "SELECT $returnFields FROM '$table' $whereClause LIMIT $limit";
$stmt = $this->db->prepare($sql);
if( $whereClause != '') {
call_user_func_array(array(&$stmt, 'bindParam'), $bind_names);
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
... which at some point I want to call using something along these lines...
// look for users in database...
$user_recs = $crud->dbSearch('user', array('user_name'), array('user_name'=> $_POST['username']));
$users = $user_recs->fetchAll(PDO::FETCH_ASSOC);
How bonkers is this? Is it possible? Do I need to pass in the param types as well somehow? Any help gratefully received!

Actually, the problem was using bound parameters as opposed to bound values... doh!
Given an SQL statement and some values in an associative array, e.g.
$sql = "SELECT * FROM event
WHERE eventdate >= :from
AND eventdate <= :until
AND ( user_name LIKE :st OR site_name LIKE :st )
ORDER BY eventdate, start_time LIMIT 100";
$values = array( 'st' => '%'.$searchterm.'%',
'from' => $fromdate,
'until' => $untildate, );
then this class method ( but it could easily by a plain function) did the trick:
public function dbBoundQuery($sql, $values, $types = false) {
$this->conn();
$stmt = $this->db->prepare($sql);
foreach($values as $key => $value) {
if($types) {
$stmt->bindValue(":$key",$value,$types[$key]);
} else {
if(is_int($value)) { $param = PDO::PARAM_INT; }
elseif(is_bool($value)) { $param = PDO::PARAM_BOOL; }
elseif(is_null($value)) { $param = PDO::PARAM_NULL; }
elseif(is_string($value)) { $param = PDO::PARAM_STR; }
else { $param = FALSE;}
if($param) $stmt->bindValue(":$key",$value,$param);
}
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
Hope this helps someone else.

I really don't understand how this function is better than conventional
$stmt = $db->prepare("SELECT user_name FROM user WHERE user_name = ?");
$stmt->execute($_POST['username']);
$users = $stmt->fetchAll();
Mind you,
it keeps your query flexible. LIMIT ?,? is possible
it keeps your query readable. Almost natural English of SQL stays in place. You still can tell what does your query do, without the need of learning some brain-damaging language. As a side effect, any other developer can comprehend this code too.

Well, there are quite a few things that can go wrong with the code.
For once, I don't see any AND / OR between WHERE clauses - which is probably why it doesn't work.
Secondly, it doesn't allow you to use SQL functions. Let's say you need to write a query like this:
SELECT * FROM `table` WHERE UNIX_TIMESTAMP(date_added) < ...;
You get the idea.
I would suggest to either use an existing ORM (Doctrine, Propel, etc), or stick to PDO.
Here is an example of how I would use PDO for a User class:
class User
{
protected $data;
public function __get($key) {
return $this->data[$key];
}
public function __set($key, $value) {
$this->data[$key] = $value;
}
/**
* #param $value
* #param $field
* #return $this
*/
public function loadBy($value, $field)
{
$db = DbFactory::getInstance();
$query = "SELECT * FROM users WHERE $field = :$field LIMIT 1";
$stmt = $db->prepare($query);
$stmt->execute(array(":$field" => $value));
$result = $stmt->fetch();
foreach ($result as $key => $value) {
$this->$key = $value;
}
return $this;
}
}
You can create such functions for your entities, this you will have functions that are specialized, and efficient in what they do, and that are easy to test.
PS:
Ignore the issue that appears when you have a field named data :)

Related

How to implement prepared statement in Zend Framework Database Query Functions

I am learning the Zend Framework. Now I need to attach a prepared statement to prevent SQL injection in the Zend Framework.
So I am sharing some functions here which I am using so if you can tell me how I can attach a prepared statement in these Zend Database Query Functions it will be helpful.
public function getRowByID($id) {
$row = $this->fetchRow("id = '$id'");
if (!$row) {
return false;
}
return $row;
}
public function getbyProjectID($projectid) {
$query = "SELECT * FROM auth where projectid = '$projectid'";
$result = $this->getAdapter()->query($query);
return $result->fetchAll();
}
public function updateRowByUserProject($username, $projectid) {
$query = "UPDATE auth SET iscurrent=0 WHERE username = '$username'";
$result = $this->getAdapter()->query($query);
$query1 = "UPDATE auth SET iscurrent=1 WHERE username = '$username' AND projectid = '$projectid'";
$result1 = $this->getAdapter()->query($query1);
$affectedRow = $result1->rowCount();
if($affectedRow == 1){
return true;
}else{
return false;
}
}
For fetching you could use Zend_Db_Select Class methods for preparing a query and executing it, passing variables in questionmark places (placeholders which will be escaped from special characters) after comma (possible multiple questionmarks, passing variables from left to right):
public function getRowByID($id) {
$table = $this->getTable();
$select = $table->select();
$select->where('id = ?', $id);
$row = $table->fetchRow($select);
if (!$row) {
return false;
}
return $row;
}
For your second method getByProjectId() it depends if you are in proper model (like Auth_Model_Auth) or you want to access data from another table
public function getbyProjectID($projectid) {
$table = $this->getTable();
$select = $table->select();
$select->where('projectid = ?', $projectid);
$result = $table->fetchAll($select);
return $result;
}
And for updating you can pass an array to 'update' method in same style like for fetching data. Key of an array must be a column name of your table.
public function updateRowByUserProject($username, $projectid) {
$table = $this->getTable();
// Data you want to insert/update
$data = [
'iscurrent' => 0
];
// Where you want to update it
$where = [
'username = ?' => $username
]
$result = $table->update($data, $where);
$data1 = [
'iscurrent' => 1
]
$where1 = [
'username = ?' => $username,
'projectid = ?' => $projectid
]
$result1 = $table->update($data1, $where1);
}
EDIT:
For both questions from comments you could achieve this by using quoteInto method, which also escapes data from special chars.
In first case you prepare a $where variable, which contains what record you want to delete:
$table = $this->getTable();
$where = $table->getAdapter()->quoteInto('projectid = ?', $projectid);
$isDeleted = $table->delete($where);
In second case you can do exactly the same:
$query = "SELECT COUNT(*) AS total FROM applications WHERE projectid IN (SELECT projectid FROM auth WHERE projectid = ?)";
$query = $this->getAdapter()->quoteInto(?, $projectid):
...
But you should try to avoid writing big queries in one variable and then executing them. I would suggest you to get to know with this:
https://framework.zend.com/manual/1.11/en/zend.db.select.html
Really well explained how to use Zend methods for this purpose.

php mysqli prepared statements select

Trying to get a function working to create simple CRUD "Select" with multiple parameters to any table. I think I got the hardest part, but couldn't fetch the data right now. Maybe I'm doing something wrong I can't figure out.
My prepared statement function:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
if($stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
} else {
$error = $mysqli->errno . ' ' . $mysqli->error;
error_log($error);
}
}
The query creator:
function create_select_query($table, $condition = "", $sort = "", $order = " ASC ", $clause = ""){
$table = escape_mysql_identifier($table);
$query = "SELECT * FROM ".$table;
if(!empty($condition)){
$query .= create_select_query_where($condition,$clause);
}
if(!empty($sort)){
$query .= " ORDER BY ".$sort." $order";
}
return $query;
}
The helper function to create the WHERE clause:
function create_select_query_where($condition,$clause){
$query = " WHERE ";
if(is_array($condition)){
$pair = array();
$size = count($condition);
$i = 0;
if($size > 1){
foreach($condition as $field => $val){
$i++;
if($size-1 == $i){
$query .= $val." = ? ".$clause. " ";
}else{
$query .= $val." = ? ";
}
}
}else{
foreach($condition as $field => $val){
$query .= $val." = ? ";
}
}
}else if(is_string($condition)){
$query .= $condition;
}else{
$query = "";
}
return $query;
}
The select function itself:
function crud_select($conn, $table, $args, $sort, $order, $clause){
$sql = create_select_query($table, array_keys($args),$sort, $order, $clause);
print_r($sql);
if($stmt = prepared_query($conn, $sql, array_values($args))){
return $stmt;
}else{
$errors [] = "Something weird happened...";
}
}
When I create the query, it seems to be OK but can't fetch the data. If I create an array with only one argument the query translates into:
SELECT * FROM `teste_table` WHERE id = ?
If I create with multiple parameters, it turns like this:
SELECT * FROM `teste_table` WHERE id = ? AND username = ?
So, how can I properly fetch the data from the select. This should be used for multiple purposes, so I could get more than one result, so the best way would be fetch data as array I guess.
I guess I'm close, but can't figure it out. Thanks
I told you to limit your select function to a simple primary key lookup. And now you opened a can of worms. As a result you are getting entangled implementation code and unreadable application code.
$table, $args, $sort, $order, $clause
What all these variables are for? How you're going to call this function - a list of gibberish SQL stubs in a random order instead of plain and simple SQL string? And how to designate a list of columns to select? How to use JOINS? SQL functions? Aliases? Why can't you just write a single SQL statement right away? You already have a function for selects, though without this barbaric error reporting code you added to it:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
}
Just stick to it and it will serve you all right.
$sql = "SELECT * FROM `teste_table` WHERE id = ? AND username = ?";
$stmt = prepared_query($mysqli, $sql, [$id, $name]);
$row = $stmt->get_result()->fetch_assoc();
The only specific select function could be, again, a simple primary key lookup:
function crud_find($conn, $table, $id)
{
$table = escape_mysql_identifier($table);
$sql = "SELECT * FROM $table WHERE id=?";
$stmt = prepared_query($conn, $sql, [$id], "i");
return $stmt->get_result()->fetch_assoc();
}
And for the everything else just use a generic function with native SQL.

(PHP) Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement

I have the following query.
$mysqldb = mysqlidb_class();
$query = "select * from post where idx < ?"
Then I bind the parameter and execute.
$bindvariable = array();
array_push($bindvariable, $post_photoidx);
array_push($bindvariable, $post_idx);
$res = $mysqldb->rawQuery($query, $bindvariable);
Then I get the following error.
Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement
But when I change the query like below, the error disappears.
$query = "select * from post where idx = ?"
What am I doing wrong here?
Here is the class I use for the mysql query
<?php
class MysqliDb
{
......
public function rawQuery ($query, $bindParams = null, $sanitize = true)
{
$this->_query = $query;
if ($sanitize)
$this->_query = filter_var ($query, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES);
$stmt = $this->_prepareQuery();
if (is_array($bindParams) === true) {
$params = array(''); // Create the empty 0 index
foreach ($bindParams as $prop => $val) {
$params[0] .= $this->_determineType($val);
array_push($params, $bindParams[$prop]);
}
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($params));
}
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return $this->_dynamicBindResults($stmt);
}
......
protected function _buildQuery($numRows = null, $tableData = null)
{
$this->_buildJoin();
$this->_buildTableData ($tableData);
$this->_buildWhere();
$this->_buildGroupBy();
$this->_buildOrderBy();
$this->_buildLimit ($numRows);
$this->_lastQuery = $this->replacePlaceHolders ($this->_query, $this->_bindParams);
if ($this->isSubQuery)
return;
// Prepare query
$stmt = $this->_prepareQuery();
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
return $stmt;
}
protected function _prepareQuery()
{
if (!$stmt = $this->_mysqli->prepare($this->_query)) {
trigger_error("Problem preparing query ($this->_query) " . $this->_mysqli->error, E_USER_ERROR);
}
return $stmt;
}
protected function refValues($arr)
{
//Reference is required for PHP 5.3+
if (strnatcmp(phpversion(), '5.3') >= 0) {
$refs = array();
foreach ($arr as $key => $value) {
$refs[$key] = & $arr[$key];
}
return $refs;
}
return $arr;
}
......
} // END class
You mightn't use array(2).
Instead, use
$sql = "select * from post where idx < :i";
$stmt->bindparam("i", 2);
$stmt->execute();
or use
$array = array($something,$else,$whatever);
$sql = "select * from post where idx < ?";
$stmt->bindparam("i", $array[2]);
$stmt->execute();
It looks like you are not preparing your query before biding parameters to it.
$sql = "SELECT * FROM post WHERE idx < ?";
if($stmt = $stmt->prepare($sql)) {
$stmt->bind_param('i', 2);
$stmt->execute();
}
Santize filters ruined my SQL query.
I have changed the source to the following to resolve the problem.
$mysqldb->rawQuery($query, $bindvariable, false);

PHP PDO DB Function returns 1 instead of value requested

I am bit confused as when try the code below I receive the desired result.
include_once('config.class.php');
$db = Core::getInstance();
$whr = 'test#nannex.com';
$inv = $db->dbh->prepare("SELECT * FROM ruj_users WHERE email=:whr");
$inv->execute(array(":whr"=>$whr));
$row = $inv->fetch(PDO::FETCH_ASSOC);
echo $row['email'];
echo $row['full_name'];
However, when I run the following code it returns 1 not the desired result.
include_once('config.class.php');
$db = Core::getInstance();
$whr = 'test#nannex.com';
function fetchUser($whr){
$db = Core::getInstance();
$inv = $db->dbh->prepare("SELECT * FROM ruj_users WHERE :whr");
$inv->execute(array(':whr'=>$whr));
$res = $inv->fetch(PDO::FETCH_ASSOC);
return $res;
}
$row = fetchUser("email = '".$whr."' ");
echo $row['email'];
echo $row['full_name'];
This query:
SELECT * FROM ruj_users WHERE :whr
When expanded:
SELECT * FROM ruj_users WHERE 'email = \'test#nannex.com\''
The expression email = \'test#nannex.com\' will be evaluated by MySQL as a boolean and is always truthy, so it will return all rows in ruj_users.
If you want custom conditions, you can do something like this:
function fetchUser(array $conditions)
{
// ...
$sql = 'SELECT * FORM ruj_users WHERE';
$params = array();
foreach ($conditions as $column => $value) {
if (preg_match('/^[a-z]+$/', $column)) {
$sql .= "`$column` = ?";
$params[] = $value;
}
}
$inv = $db->dbh->prepare($sql);
$inv->execute(array_values($params));
// ...
}
fetchUser(array(
'email' => 'test#nannex.com',
'status' => 23,
));
You have an error in your query in the function:
$inv = $db->dbh->prepare("SELECT * FROM ruj_users WHERE :whr");
Should be:
$inv = $db->dbh->prepare("SELECT * FROM ruj_users WHERE email=:whr");
Edit: If you want to pass column names as well, you would have to add another variable to your function:
function fetchUser($column, $value) {
Note that only the value can be bound in a prepared statement, the column variable you would have to check against a white-list to avoid sql injection and hard-code in the query, like ... WHERE $column = :whr ...

FetchList() in zend Framework not working

I need to run a query in zend frawork. The query is as given below.
select * from users where role_id in (3,5) and emailID not in ('abc#abc.com', 'cba#cba.com');
I am passing the string
$whereString = role_id in (3,5) and emailID not in ('abc#abc.com', 'cba#cba.com');
to
$this->fetchList($whereString) // where $this is object of users
But fetchList() function executes only the first part i.e role_id in (3,5)
but not the second part i.e emailID not in ('abc#abc.com', 'cba#cba.com');
The result from fetchList() contains 'abc#abc.com' and 'cba#cba.com'
fetchList() is:
public function fetchList($where=null, $order=null, $count=null, $offset=null)
{
$resultSet = $this->getDbTable()->fetchAll($where, $order, $count, $offset);
$entries = array();
foreach ($resultSet as $row)
{
$entry = new Application_Model_Users();
$entry->setId($row->id)
->setName($row->name)
->setEmail($row->email)
$entries[] = $entry;
}
return $entries;
}
fetchAll():
/**
* Fetches all rows.
*
* Honors the Zend_Db_Adapter fetch mode.
*
* #param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
* #param string|array $order OPTIONAL An SQL ORDER clause.
* #param int $count OPTIONAL An SQL LIMIT count.
* #param int $offset OPTIONAL An SQL LIMIT offset.
* #return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
*/
public function fetchAll($where = null, $order = null, $count = null, $offset = null)
{
if (!($where instanceof Zend_Db_Table_Select)) {
$select = $this->select();
if ($where !== null) {
$this->_where($select, $where);
}
if ($order !== null) {
$this->_order($select, $order);
}
if ($count !== null || $offset !== null) {
$select->limit($count, $offset);
}
} else {
$select = $where;
}
print_r($select);
$rows = $this->_fetch($select);
$data = array(
'table' => $this,
'data' => $rows,
'readOnly' => $select->isReadOnly(),
'rowClass' => $this->getRowClass(),
'stored' => true
);
$rowsetClass = $this->getRowsetClass();
if (!class_exists($rowsetClass)) {
require_once 'Zend/Loader.php';
Zend_Loader::loadClass($rowsetClass);
}
return new $rowsetClass($data);
}
Any body knows what wrong did I do.
I'm going out on a bit of limb, because I'm not sure. This feels like it might be a quote issue. Maybe try passing your query string like:
//use Zend_Db_Statement to process raw SQL.
//get default adapter
$db = Zend_Db_Table::getDefaultAdapter();
//write SQL statement
$sql = "select * from users where role_id in (3,5) and emailID not in ('abc#abc.com', 'cba#cba.com')";
//assuming MySql as DB instantiate object
$stmt = new Zend_Db_Statement_Mysqli($db, $sql);
//Execute SQL Statement accepts optional parameter for array() of values to be bound
$stmt->execute();
or you might like to build the query using the select() object:
//get default adapter
$db = Zend_Db_Table::getDefaultAdapter();
//instantiate the select() object
$select = new Zend_Db_Select($db);
$select->from('users');
//two where will build an AND query use $select->orWhere() to build an OR query
$select->where('role_id in (3,5)');
$select->where("emailID not in ('abc#abc.com', 'cba#cba.com')");
$result = $model->fetchList($select);
and it might work if $whereString were a valid PHP string, beacuse fetchAll() accepts a string or an instance of Zend_Db_Select:
$whereString = "role_id in (3,5) and emailID not in ('abc#abc.com', 'cba#cba.com')";
Hope this helps some... I took a shot.

Categories