I've written a php function that allows you to update any entry in any table with any string values (one or multiple). PDO does not throw any errors, though the script does not seem to work! I've checked the name of the database, tables and fields multiple times. It's all correct. This is the only query in my functions that does not work. I believe it has something to do with the array im passing in to the SQL statement and the PDO->bindParam() function.
Code:
public function updateTableDetail($table, $id, $params) {
include($this->doc_root . 'config/config.php');
if (is_array($params)) {
foreach ($params as $param) {
$param = Utilities::escapeString($param);
}
} else {
throw new InvalidInputException(InputErrors::NOTANARRAY);
}
if (is_nan($id)) throw new InvalidInputException(InputErrors::NOTANUMBER);
$table = Utilities::escapeString($table);
$sql = "UPDATE " . $table . "
SET " . $config['table_field_updated'] . " = :updated";
while (current($params)) {
$sql .= "," . key($params) . " = :" . key($params);
next($params);
}
reset($params);
$sql .= " WHERE id = :id
AND " . $config['userId'] . " = :userId";
if ($this->serverConnector == null) {
$this->serverConnector = new ServerConnector();
}
if ($this->db == null) {
$this->db = $this->serverConnector->openConnectionOnUserDb($this->dbname);
}
$stmt = $this->db->prepare($sql);
$updated = date("Y-m-d H:i:s");
$stmt->bindParam(':updated',$updated);
$stmt->bindParam(':id',$id);
$stmt->bindParam(':userId',$this->userId);
while ($param = current($params)) {
$stmt->bindParam(":".key($params),$param);
next($params);
}
reset($params);
$stmt->execute();
}
EDIT: Don't worry about the include statement, the $config[]-array and the class-variables. It's all working aswell. Tested their values already.
Change this part:
while ($param = current($params)) {
$stmt->bindParam(":".key($params),$param);
next($params);
}
To:
foreach($params as $key => &value){
$stmt->bindParam(":$key",$value);
}
Because according to PHP Manual: PDOStatement::bindParam
Binds a PHP variable to a corresponding named or question mark
placeholder in the SQL statement that was use to prepare the
statement. Unlike PDOStatement::bindValue(), the variable is bound as
a reference and will only be evaluated at the time that
PDOStatement::execute() is called.
Related
I have a very simple query that works when I don't use parameters. With parameters, it returns nothing. Someone else posted the same issue over here:
Query with input parameters doesn't work whereas direct query works
However no one has answered it. Below is my code.
require_once('database.class.php');
class Plan extends Database {
public function getBenefitAmounts($plan_id, $group_id, $level) {
$sql = 'SELECT DISTINCT benefit FROM rates WHERE plan_id = :plan AND group_id IS NULL AND `level` = :lvl';
$params = array(':plan'=>896, ':lvl'=>1);
$this->sqlQuery($sql, $params);
// $sql = 'SELECT DISTINCT benefit FROM rates WHERE plan_id = 896 AND group_id IS NULL AND `level` = 1';
// $this->sqlQuery($sql);
$results = $this->sth->fetchAll(PDO::FETCH_COLUMN);
$options = '';
foreach ($results as $value) {
$options .= '<option value="' . $value . '">$' . $value . '</option>';
}
return $options;
}
}
In the database class:
public function sqlQuery($sql, $values_to_bind=null) {
$this->sth = $this->pdo->prepare($sql);
if (isset($values_to_bind)) {
foreach ($values_to_bind as $param => $value) {
$this->sth->bindValue($param, $value);
}
}
$success = $this->sth->execute();
if (!$success) {
$arr = $this->$sth->errorInfo();
print_r($arr);
}
}
The code commented out of the first code snippet works just fine, but with parameters, it returns nothing. The getBenefitAmounts function is called from another PHP file which is called using a JQuery get.
Did you try to add the third param which is optional for bindValue(). It can be something like PDO::PARAM_INT, PDO::PARAM_STR etc. Just try to debug and see if it helps.
I don't know why do you like so much try...catch if you use with no sense code. Because this technique: } catch (PDOException $e) { throw new PDOException($e);} means the same as } catch (PDOException $e) {;} like you ask php to do nothing if catch. Why do you asked to catch if you do nothing in case it happens?
Now my guess about how to fix your code:
public function sqlQuery($sql, $values_to_bind=null) {
$this->sth = $this->pdo->prepare($sql);
if (isset($values_to_bind)) {
foreach ($values_to_bind as $param => $value) {
$this->sth->bindValue($param, $value);
}
}
$success = $this->sth->execute();
if (!$success) {
$arr = $this->$sth->errorInfo();
print_r($arr);
}
}
By the way you use $this->sth = $this->pdo->prepare($sql); that means your sqlQuery function is a method of some class that you didn't show to us. and your first piece of code is somewhere outside that class? it would be better if you post full version of code, not just lines you think are involved.
and here you can switch to regular way:
//$results = $this->sth->fetchAll(PDO::FETCH_COLUMN); //you don't need it
$options = '';
while ($row = $this->sth->fetch(PDO::FETCH_ASSOC)) {
$options .= '<option value="' . $row['benefit'] . '">$' . $row['benefit'] . '</option>';
}
Why not bind the parameters using bindParam?
$plan = 896;
$lvl = 1;
$sth = $dbh->prepare("SELECT DISTINCT benefit FROM rates WHERE plan_id = :plan AND group_id IS NULL AND `level` = :lvl");
$sth->bindParam(":plan", $plan);
$sth->bindParam(":lvl", $lvl);
$sth->execute();
$r = $sth->fetchAll();
I'm made a database class in php. Now i was testing the update function in it. It returns an syntax error or an unknown column error depending on how the where-clause is formed.
I tried:
'woord = \'uiteindelijk\'' and 'woord = \"uiteindelijk\"' and
'woord = "uiteindelijk"' and more
I also tried different quotes and backsticks in de functions query but it al gave me the same errors.
My question is what is the right way to form the where-clause is this example if it possible ofcourse. And if not how can i fix it.
part of database.mysqli.php
<?php
class myDB {
private $mysqli;
public function __construct() {
require_once('config.php');
$this->mysqli = new mysqli(HOST, USERNAME, PASSWORD, DB_NAME);
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
}
public function close() {
$this->mysqli->close();
}
private function check($input) {
if(is_string($input)) {
$input = trim($input);
return $this->mysqli->real_escape_string($input);
}
elseif(is_int($input)) {
return $input;
}
elseif($input === true || $input === false) {
return $input;
}
else {
die('invalid input');
}
}
public function update($table, $data, $where) {
$table = $this->check($table);
$where = $this->check($where);
$result = '';
if (is_array($data)) {
$update = array();
foreach ($data as $key => $val) {
$key = $this->check($key);
$val = $this->check($val);
$update[] .= $key . '=\'' . $val . '\'';
}
$query = 'UPDATE ' . $table . ' SET ' . implode(',', $update) . ' WHERE ' . $where;
if($this->mysqli->query($query)) {
$result = 'Last updated row id is: '.$this->mysqli->insert_id;
}
else {
$result = $this->mysqli->error;
}
}
return $result;
}
test.php
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<?php
require_once('database.mysqli.php');
$db = new myDB;
$data = array('woord' => 'gewoontjes', 'lengte' => 10);
$result = $db->update('galgje', $data, 'woord = \'uiteindelijk\'');
echo $result;
$db->close();
?>
</body>
</html>
The problem comes from escape_string in your check method. This function is used to escape precise parts inside a statement, you cannot apply it to the where clause as a whole in such a generic way.
If you ever know for sure that your inputs are safe (not containing special characters breaking the sql statement, malicious or not), then simply remove the escaping.
Or if you think that they may contain special characters, for good reasons or to possibly drag a sql injection, then you have to provide a more constrained interface so that you can build the where clause yourself with the appropriate escaping. For example :
public function update($table, $data, $woord) {
...
$where = 'woord = \'' . $this->check($woord) . '\'';
...
}
Edit: I know it may sound too much constrained but security comes at a price. For something more flexible, you could have a look at prepared statements. They let you use placeholders, for example WHERE woord = ? AND id < ?, which you can bind to variables with something like :
$stmt->bind_param('si', $woord, $id); // 'si' -> 1:string, 2:integer
In this case, mysqli applies escaping internaly on bound strings, so you don't have to worry about it.
Note that you cannot use a placeholder to replace the whole where clause. WHERE ? with $stmt->bind_param('s', $where); will not work.
Last thing, PDO, an alternative API to access your database in PHP, supports named placeholders (WHERE woord = :woord instead of WHERE woord = ?).
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'));
So, I am passing arrays of values that will vary upon use into a method that then inserts them into a database. My problem is the way in which the parameters are bound.
public function insertValues($table, $cols, $values)
{
$mysqli = new mysqli(DBHOST, DBUSER, DBPASSWORD, DBDATABASE);
$colString = implode(', ', $cols); // x, x, x
$valString = implode(', ', array_fill(0, count($values), '?')); // ?, ?, ?
$sql = "INSERT INTO $table ($colString) VALUES($valString)";
if (!$stmt = $mysqli->prepare($sql))
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
// THIS IS THE PROBLEM AREA
foreach ($values as $v)
if (!$stmt->bind_param('s', $v))
echo "Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error;
if (!$stmt->execute())
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
$stmt->close();
$mysqli->close();
}
I need a way to bind all the parameters at once I think, and not one at a time, but I can't figure out a useful way to do this. Any help would be greatly appreciated.
I found the answer for the problem you are looking for on PHP.net (http://php.net/manual/en/mysqli-stmt.bind-param.php). I'm pasting it here for convenience, all credit goes to a man going by the email Nick9v ^ät^ hotmail -remove- -dot- com
When dealing with a dynamic number of field values while preparing a
statement I find this class useful.
[Editor's note: changed BindParam::add() to accept $value by reference
and thereby prevent a warning in newer versions of PHP.]
<?php
class BindParam{
private $values = array(), $types = '';
public function add( $type, &$value ){
$this->values[] = $value;
$this->types .= $type;
}
public function get(){
return array_merge(array($this->types), $this->values);
}
}
?>
Usage is pretty simple. Create an instance and use the add method to populate. When you're ready to execute simply use the get method.
<?php
$bindParam = new BindParam();
$qArray = array();
$use_part_1 = 1;
$use_part_2 = 1;
$use_part_3 = 1;
$query = 'SELECT * FROM users WHERE ';
if($use_part_1){
$qArray[] = 'hair_color = ?';
$bindParam->add('s', 'red');
}
if($use_part_2){
$qArray[] = 'age = ?';
$bindParam->add('i', 25);
}
if($use_part_3){
$qArray[] = 'balance = ?';
$bindParam->add('d', 50.00);
}
$query .= implode(' OR ', $qArray);
//call_user_func_array( array($stm, 'bind_param'), $bindParam->get());
echo $query . '<br/>';
var_dump($bindParam->get());
?>
This gets you the result that looks something like this:
SELECT * FROM users WHERE hair_color = ? OR age = ? OR balance = ?
array(4) { [0]=> string(3) "sid" 1=> string(3) "red" [2]=> int(25) [3]=> float(50) }
The code doesn't work because bind_param has to have all of the query parameters in a single call to the function instead of multiple calls for each parameter, also it needs variables passed by reference, so in the foreach call it would always be the same variable with the value that it had in the last iteration of the loop.
The easiest way would be to compose an array with the types and parameters, and then pass it to bind_param with a call to call_user_func_array, for example:
$params = array();
$types = '';
foreach ($values as $k => $v)
{
$types .= 's';
$params[] = &$values[$k];
}
$bind_params = array_merge(array($types), $params);
if (!call_user_func_array(array($stmt, 'bind_param'), $bind_params))
// ...
Note that bind_param expects variables to be passed by reference not by value, otherwise it would be a couple of lines constructing an array with values, instead of the foreach loop.
I'm having trouble finding good documentation on pdo update prepared statements and even more trouble finding documentation on dynamically updating the database with pdo prepared statements. I've gotten my dynamic insert to work but am having trouble with the update. The error I'm getting is:
Warning: PDOStatement::execute() [pdostatement.execute]:
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
in
/Users/scottmcpherson/Sites/phpsites/projectx/application/models/db.php
on line 91 error
Here is the class I created minus a couple of methods that are irrelevant to this problem:
<?php
require_once("../config/main.php");
class Database{
protected static $dbFields = array('username', 'password');
public $db;
public $tableName = 'users';
public $id = 1;
public $username = "Jonny";
public $password = "Appleseed";
public function __construct() {
$this->connect();
}
public function connect(){
try {
$this->db = new PDO("mysql:host=".DB_SERVER."; dbname=".DB_NAME, DB_USER, DB_PASS);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
public function properties() {
$properties = array();
foreach (self::$dbFields as $field) {
if (isset($this->field) || property_exists($this, $field)) {
$properties[$field] = $this->$field;
}
}
return $properties;
}
public function propertyValues() {
$property = $this->properties();
$propertyValues = array();
foreach ($property as $key => $value) {
$propertyValues = ":" . implode(", :", array_keys($property));
}
return $propertyValues;
}
public function polishedVals(){
// The end result of this function is:
// username=:username, password=:password
$props = $this->properties();
$phaseOne = array();
foreach ($props as $key => $value) {
$phaseOne[$key] = ":".$key;
}
$phaseTwo = array();
foreach ($phaseOne as $key => $value) {
$phaseTwo[] = "{$key}={$value}";
}
$polishedVals = implode(", ", $phaseTwo);
return $polishedVals;
}
public function update(){
$stmt = "UPDATE ". $this->tableName." SET ";
$stmt .= $this->polishedVals();
$stmt .= "WHERE id=" . $this->id;
$stmt = $this->db->prepare($stmt);
if($stmt->execute($this->properties())) {
echo "yes";
} else {
echo "error ";
}
}
}
$database = new Database();
echo$database->update();
?>
With all the variables replaced with the actual values, the result I'm going for with the update() method would look like this:
public function update(){
$stmt = "UPDATE users SET ";
$stmt .= "username=:username, password=:password ";
$stmt .= "WHERE id=1";
$stmt = $this->db->prepare($stmt);
if($stmt->execute($this->properties())) {
echo "yes";
} else {
echo "error ";
}
}
In addition to spotting this problem, please let me know if you see any other issues with this code. I'm still kind of new to PHP.
Edit: I've now created a new method that adds a : to the beginning of each key in the properties array:
public function colProperties(){
$properties = $this->properties();
$withCols = array();
foreach($properties as $key => $value){
$withCols[":".$key] = $value;
}
return $withCols;
}
So my update() method now looks like:
public function update(){
$stmt = "UPDATE ". $this->tableName." SET ";
$stmt .= $this->polishedVals();
$stmt .= "WHERE id=" . $this->id;
$stmt = $this->db->prepare($stmt);
if($stmt->execute($this->colProperties())) {
echo "yes";
} else {
echo "error ";
}
}
and if I var_dump($this->colProperties) I get:
array(2) { [":username"]=> string(5) "Jonny" [":password"]=> string(9) "Appleseed" }
And still getting the same error.
I don't think that passing parameters to an UPDATE query requires a different method than a SELECT one. The information in the PDOStatement->execute() manual page should apply:
<?php
/* Execute a prepared statement by passing an array of insert values */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour');
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
?>
You are using named parameters so execute() expects an associative array. Use var_dump() to display $this->properties() right before execute():
var_dump($this->properties())
Make sure you keys match exactly.
The error is that in between
$stmt .= $this->polishedVals();
$stmt .= "WHERE id=" . $this->id;
There needs to be a space in between the WHERE clause as the polishedVals() method does not add a space after the implode. So, you'll have something like
UPDATE User SET city=:city, location=:locationWHERE User.id=28
Which causes the error.
Simple bug.