Invalid number of parameters binding PDO - php

I am using a tutorial for this project, however I am trying to expand on what was provided in the material. I am trying to use a function which creates the query just before binding the values to create a query with more than 3 WHERE values.
Here is the code:
private function action($action, $table, $where = array()){
$operators = array('=', '>', '<', '>=', '<=' , 'AND' ,'OR', 'LIKE', 'GROUP BY','ORDER BY', 'ASC', 'DESC');
if(!empty($where)){
$sql = "{$action} FROM {$table} WHERE ";
if(count($where) > 3){
$isVal = FALSE;
$values = '';
foreach ($where as $value) {
switch(trim($value)){
case '=':
case '>':
case '<':
case '>=':
case '<=':
$sql .= "{$value}";
$isVal = true;
break;
default:
if($isVal){
$sql .= " ? ";
$values .= $value;
$isVal = false;
}else{
$sql .= "{$value}";
}
break;
}
}
if(!$this->query($sql, $values)->error()){return $this;}
/////////////////////////////////////////
// From this point down everything works!!!
////////////////////////////////////////////
}else if(count($where) === 3){
$field = $where[0];
$operator = $where[1];
$value = $where[2];
if(in_array($operator, $operators)){
$sql = "{$action} FROM {$table} WHERE {$field} {$operator} ?"; // NO $value ?
if(!$this->query($sql, array($value))->error()){return $this;}
}
}
}else{
// If array is empty
$sql = "{$action} FROM {$table}";
if(!$this->query($sql)->error()){return $this;}
}
return FALSE;
}
Section where nested Else IF statement reads count($where) === 3 works fine, however the first nested IF 'count($where > 3)` throws me an error.
I am trying to find a way to set this up correctly so that I can use more than just 3 where values.
Also here is my query binder:
public function query($sql, $params = array()){
$this->_error = FALSE;
if($this->_query = $this->_pdo->prepare($sql)){
$x = 1;
if(count($params)){
foreach($params as $param){
$this->_query->bindValue($x, $param);
$x++;
}
}// End IF count
if($this->_query->execute()){
$this->_lastID = $this->_pdo->lastInsertId();
try{
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
}catch(Exception $e){
// Catch Error
}
$this->_count = $this->_query->rowCount();
}else{
$this->_error = TRUE;}
}
return $this;
}
If anyone can help me out with this I will be very grateful ... Thank you!

There are some problems here, for example:
$values in your first method is a concatenated string. Casting that to an array will give you an array with 1 element which is not what you need. You would need an array with all values separated, something like $values[] = $value; instead of $values .= $value;.
You will run into problems when you try to add combinations of OR and AND statements as grouping using parenthesis makes a lot of difference there.
You are using a prepared statement for the values but there is no check on the column names to prevent sql injection. You should use a white-list there.

Related

Error (SQLSTATE[HY000]: General error) in oop

I start to get this error when i add something in database i know what the problem but i tried to solve it but i could not
part of database class:
public function query($sql, $params = array()) {
$this->_error = FALSE;
$this->_query = $this->_conn->prepare($sql);
if ($this->_query) {
if (count($params)) {
$x = 1;
foreach ($params as $param) {
$this->_query->bindValue($x, $param);
$x++;
}
}
if ($this->_query->execute()) {
$this->_result = $this->_query->fetchAll(PDO::FETCH_OBJ);
$this->_count = $this->_query->rowCount();
} else {
return $this->_error = TRUE;
}
}
return $this;
}
public function insert($table, $fields = array()){
$keys = array_keys($fields);
$value = NULL;
$x = 1;
foreach ($fields as $field) {
$value .= '?';
if ($x < count($fields)) {
$value .=' , ';
}
$x++;
}
$sql = 'INSERT INTO ' . $table . '(`' . implode('`,`', $keys) . '`) VALUES (' . $value . ')';
if (!$this->query($sql, $fields)->error()) {
return TRUE;
}
return FALSE;
}
this when i insert the data in database
try {
$db->insert('users', array('username' => $_POST['username'],
'password' => $pass_hash,
'Group' => 0));
} catch (Exception $ex) {
die($ex->getMessage());
}
everything work fine and code insert the data in database but problem i get this error (SQLSTATE[HY000]: General error) because i am trying to fetch the data and i should not fetch it but i want my class be more dynamic , what the way i can fix this ? and thank you
problem form this line
$this->_result = $this->_query->fetchAll(PDO::FETCH_OBJ);

php Error trying to make a OOP login system

I keep getting the warning error while trying to make this object oriented log in system. I am trying to do the insert function right now but the warning is stopping me.
Warning: Invalid argument supplied for foreach() in C:\xampp\htdocs\YetiDraft\yetidb\classes\db.php on line 32
Line 32 is the function query foreach statement
<?php
class db {
private static $_instance = null;
private $_pdo,
$_query,
$_error = false,
$_results,
$_count = 0;
private function __construct() {
try {
$this->_pdo = new PDO('mysql:host=' . config::get('mysql/host') . ';dbname=' . config::get('mysql/db'), config::get('mysql/username'), config::get('mysql/password'));
} catch(PDOException $e) {
die($e->getMessage());
}
}
public static function getInstance(){
if(!isset(self::$_instance)){
self::$_instance = new db();
}
return self::$_instance;
}
public function query($sql, $params = array()) {
$this->_error = false;
if($this->_query = $this->_pdo->prepare($sql)) {
$x = 1;
if(count($params)) {
foreach($params as $param) {
$this->_query->bindValue($x, $param);
$x++;
}
}
if($this->_query->execute()){
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
$this->_count = $this->_query->rowCount();
}else{
$this->_error = true;
}
}
return $this;
}
public function action($action, $table, $where = array()){
if(count($where) === 3){
$operators = array('=', '>', '<', '>=', '<=',);
$field = $where[0];
$operator = $where[1];
$value = $where[2];
if(in_array($operator, $operators)){
$sql = "{$action} FROM {$table} WHERE {$field} {$operator} ?";
if(!$this->query($sql, array($value))->error()){
return $this;
}
}
}
return false;
}
public function get($table, $where){
return $this->action('SELECT *', $table, $where);
}
public function delete($table, $where){
return $this->action('DELETE', $table, $where);
}
public function insert($table, $fields = array()){
if(count($fields)){
$keys = array_keys($fields);
$values = '';
$x = 1;
foreach($fields as $fields){
$values .= "?";
if($x < count($fields)){
$values .= ', ';
}
$x++;
}
$sql = "INSERT INTO users (`" . implode('`, `', $keys) ."`) VALUES ({$values})";
if(!$this->query($sql, $fields)->error()){
return true;
}
}
return false;
}
public function results(){
return $this->_results;
}
public function first(){
return $this->results()[0];
}
public function error(){
return $this->_error;
}
public function count() {
return $this->_count;
}
}
?>
This part might cause the problem:
foreach($fields as $fields){
$values .= "?";
if($x < count($fields)){
$values .= ', ';
}
$x++;
}
You are using the variable $fields both as source and as iterator.
Doing so overwrites $fields with the last element of the array. For this reason, in the following call, $fields is not an array anymore:
if(!$this->query($sql, $fields)->error()){
And since you are trying to iterate over the value of $fields in $this->query(), you are getting the error.
Thanks to Barmar for the tip!
Try this instead:
foreach($fields as $field){
$values .= "?";
if($x < count($fields)){
$values .= ', ';
}
$x++;
}
Edit: Or use Barmar's solution instead. His solution is better.
My guess is that $params isnt an array and hence isnt traversable. Make a small check before the foreach like this.
if (is_array($params))
{
foreach ($params as $param)
{
//do things here
}
}
count() is not a reliable way to check if a variable is an array. Use is_array instead of count(). Your error probably is the variable isnt an array, and you are trying to traverse through the variable.
Replace the loop:
foreach($fields as $field){
$values .= "?";
if($x < count($field)){
$values .= ', ';
}
$x++;
}
with
$values = implode(', ', array_fill(0, count($fields), '?'));
Because you're reusing the variable $fields as the iteration variable, when your loop is done $fields no longer contains an array of fields, it contains the value of the last field. So when you call $this->query($sql, $fields), you're passing a string instead of an array, and the foreach inside query() won't work.

dynamic prepared insert statement

Let me preface that I just started learning prepared statements so much of this might just be to much to grasp, but I want to try.
I am trying to make a dynamic create function within my DatabaseObject class. The function would take any number of values of potentially any number of the different allowed data types. Unfortunately nothing I have tried has worked. Here is the code.
public function create() {
$db = Database::getInstance();
$mysqli = $db->getConnection();
//array of escaped values of all types in the object
$attributes = $this->sanitized_attributes();
$check = $mysqli->stmt_init();
$paramType = array();
$types = ''; $bindParam = array(); $where = ''; $count = 0;
foreach($attributes as $key=>$val)
{
$types .= 'i';
$bindParam[] = '$p'.$count.'=$param["'.$key.'"]';
$where .= "$key = ? AND ";
$count++;
}
$sql_query = "INSERT INTO `".static::$table_name."` ";
$sql_query .= "VALUES (";
foreach ($attributes as $key => $value) {
$valueType = gettype($value);
if ($valueType == 'string') {
$sql_query .= "?,";
array_push($paramType, "s");
} else if ($valueType == 'integer') {
$sql_query .= "?,";
array_push($paramType, "i");
} else if ($valueType == 'double') {
$sql_query .= "?,";
array_push($paramType, "d");
} else {
$sql_query .= "?,";
array_push($paramType, "b");
}
}
$sql_query .= ")";
}
At this point I am completely lost as to what I am suppose to do.
I have gotten simple prepared statements to work, but this one is much more complicated and dynamic and I don't know if I handled the process up to this point correctly and what to do following the sql_query in order to get this to work. All the questions here have left me confused so maybe if I got guidance with my current code to see where i went wrong it will assist.
I appreciate your time.
public function create() {
$db = Database::getInstance();
$mysqli = $db->getConnection();
$attributes = $this->sanitized_attributes();
$tableName = static::$table_name;
$columnNames = array();
$placeHolders = array();
$values = array();
foreach($attributes as $key=>$val)
{
// skip identity field
if ($key == static::$identity)
continue;
$columnNames[] = '`' . $key. '`';
$placeHolders[] = '?';
$values[] = $val;
}
$sql = "Insert into `{$tableName}` (" . join(',', $columnNames) . ") VALUES (" . join(',', $placeHolders) . ")";
$statement = $mysqli->stmt_init();
if (!$statement->prepare($sql)) {
die("Error message: " . $mysqli->error);
return;
}
$bindString = array();
$bindValues = array();
// build bind mapping (ssdib) as an array
foreach($values as $value) {
$valueType = gettype($value);
if ($valueType == 'string') {
$bindString[] = 's';
} else if ($valueType == 'integer') {
$bindString[] = 'i';
} else if ($valueType == 'double') {
$bindString[] = 'd';
} else {
$bindString[] = 'b';
}
$bindValues[] = $value;
}
// prepend the bind mapping (ssdib) to the beginning of the array
array_unshift($bindValues, join('', $bindString));
// convert the array to an array of references
$bindReferences = array();
foreach($bindValues as $k => $v) {
$bindReferences[$k] = &$bindValues[$k];
}
// call the bind_param function passing the array of referenced values
call_user_func_array(array($statement, "bind_param"), $bindReferences);
$statement->execute();
$statement->close();
return true;
}
I want to make special note that I did not find the solution myself. I had a long time developer find this solution and wanted to post it for those that might want to know.
I accidently found your old post as I was trying myself to find a solution to the exact same problem. My code seems a bit more advantagous as there is only one loop included. Therefore I will add it as a possible improvement to this post:
$sqlquery = $this->MySQLiObj->prepare($dummy);
$paramQuery = array();
$paramQuery[0] = '';
$n = count($valueArray);
for($i = 0; $i < $n; $i++) {
$checkedDataType = $this->returnDataType($valueArray[$i]);
if($checkedkDataType==false) {
return false;
}
$paramQuery[0] .= $checkedDataType;
/* with call_user_func_array, array params must be passed by reference -> & */
$paramQuery[] = &$valueArray[$i];
}
/*In array(): sqlquery(object)->bind_param(method)*/
call_user_func_array(array($sqlquery, 'bind_param'), $paramQuery);
$sqlquery->execute();
/*Can be used identical to $result = $mysqli->query()*/
$result = $this->MySQLiObj->get_result();
$sqlquery->close();
Utilizing the function returnDataType() with a switch statement, which might be faster if there is a preference for a certain data type.
private function returnDataType($input) {
switch(gettype($input)) {
case string: return 's';
case double: return 'd';
case integer: return 'i';
default: $this->LOG->doLog("Unknown datatype during database access."); return 's';
}
}

PDO rowCount keeps returning -1 with PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL

I am puzzled with this problem for hours now. I have PHP code using PDO to access Sybase database. The problem is the function of rowCount() keeps returning the value of -1 at all times. I found the solution here PDO::rowCount() returning -1 and the user supply the parameter of PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL while preparing the query. That solution works for him but for some reason not for me. Appreciate if you guys can help me on this.
Here is my code:
public function query($sql, $params = array()){
$this->_error = false; //always first initialize to false
if( $this->_query = $this->_pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL)) ){
$x = 1;
if( count($params) ){
foreach($params as $param){
$this->_query->bindValue($x, $param);
$x++;
}
}
if( $this->_query->execute() ){
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
echo $this->_count = $this->_query->rowCount(); //this line returns -1
} else {
$this->_error = true;
}
}
return $this;
}
I will make a call to this function using something like this:
$data = $this->_db->query( "SELECT username FROM users WHERE username=?", array($user) );
Thank you.
I post my this own solution so that it helps somebody out there who might have same problem. So I followed other people solution to first calculate the rows returned and then if the number of rows is more than zero, we proceed the execution. My code is as below:
$sql = "SOME SQL STATEMENT HERE";
if( $this->_query = $this->_pdo->prepare( "SELECT COUNT(*) as computedRow FROM ( {$sql} ) AS X", array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL) ) ){
$x = 1;
if( count($params) ){
foreach($params as $param){
$this->_query->bindValue($x, $param);
$x++;
}
}
if( $this->_query->execute() ){
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
foreach ($this->_results as $obj){
$this->_count = $obj->computedRow;
}
if($this->_count){
if( $this->_query = $this->_pdo->prepare( $sql . $orderby , array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL) ) ){
$x = 1;
if( count($params) ){
foreach($params as $param){
$this->_query->bindValue($x, $param);
$x++;
}
}
if( $this->_query->execute() ){
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
}
else {
$this->_error = true;
}
}
}
else{
$this->_count = 0;
}
}
else {
$this->_error = true;
}
}
Hope this could help somebody out there. Enjoy :)

PHP PDO SQL Server database issue

I'm using SQL Server as a database and PHP PDO to connect, When creating a registration page, I get this error when executing the query
Notice: Array to string conversion in C:\webdev\classes\DB.php on line 36
There was a problem creating an account.PHP Notice: Array to string conversion in C:\webdev\classes\DB.php on line 36
Line 36 - $this->_query->bindValue($x, $param);
<?php
class DB {
public static $instance = null;
private $_pdo = null,
$_query = null,
$_error = false,
$_results = null,
$_count = 0;
private function __construct() {
try {
$this->_pdo = new PDO('sqlsrv:server=' . Config::get('sqlsrv/servername') . ';database=' . Config::get('sqlsrv/db'), Config::get('sqlsrv/username'), Config::get('sqlsrv/password'));
} catch(PDOExeption $e) {
die($e->getMessage());
}
}
public static function getInstance() {
// Already an instance of this? Return, if not, create.
if(!isset(self::$instance)) {
self::$instance = new DB();
}
return self::$instance;
}
public function query($sql, $params = array()) {
$this->_error = false;
if($this->_query = $this->_pdo->prepare($sql)) {
$x = 1;
if(count($params)) {
foreach($params as $param) {
/* Line 36 */ $this->_query->bindValue($x, $param);
$x++;
}
}
if($this->_query->execute()) {
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
$this->_count = $this->_query->rowCount();
} else {
$this->_error = true;
}
}
return $this;
}
public function get($table, $where) {
return $this->action('SELECT *', $table, $where);
}
public function delete($table, $where) {
return $this->action('DELETE', $table, $where);
}
public function action($action, $table, $where = array()) {
if(count($where) === 3) {
$operators = array('=', '>', '<', '>=', '<=');
$field = $where[0];
$operator = $where[1];
$value = $where[2];
if(in_array($operator, $operators)) {
$sql = "{$action} FROM {$table} WHERE {$field} {$operator} ?";
if(!$this->query($sql, array($value))->error()) {
return $this;
}
}
return false;
}
}
public function insert($table, $fields = array()) {
$keys = array_keys($fields);
$values = null;
$x = 1;
foreach($fields as $value) {
$values .= "?";
if($x < count($fields)) {
$values .= ', ';
}
$x++;
}
$sql = "INSERT INTO {$table} (`" . implode('`, `', $keys) . "`) VALUES ({$values})";
if(!$this->query($sql, $fields)->error()) {
return true;
}
return false;
}
public function update($table, $id, $fields = array()) {
$set = null;
$x = 1;
foreach($fields as $name => $value) {
$set .= "{$name} = ?";
if($x < count($fields)) {
$set .= ', ';
}
$x++;
}
$sql = "UPDATE users SET {$set} WHERE id = {$id}";
if(!$this->query($sql, $fields)->error()) {
return true;
}
return false;
}
public function results() {
// Return result object
return $this->_results;
}
public function first() {
return $this->_results[0];
}
public function count() {
// Return count
return $this->_count;
}
public function error() {
return $this->_error;
}}
Why would this be, I used the same code and had a mysql database and it sent the data to the db no problems, why would it be the case for SQL Server?
One of the $param iterations is coming in as an array probably:
if(count($params)) {
foreach($params as $param) {
if(is_array($param) || is_object($param)){ $param=''; }
$this->_query->bindValue($x, $param);
$x++;
}
}
Recommendation for debugging public function insert()
// add a debug parameter
public function insert($table, $fields = array(), $debug = false) {
$return = false;
if(is_array($fields) && count($fields) > 0 && $table != ''){
// build SQL and debug SQL
$sql = "INSERT INTO '$table' (";
$debug_sql = $sql;
// declare variables
$sql_fields = '';
$values = '';
$debug_values = '';
foreach($fields as $k=>$v) {
// encase fields and values in quotes
$sql_fields.= "'$k',";
$values.= "?,";
$debug_values.= "'$v',";
}
// remove trailing commas
$sql_fields = substr($sql_fields, 0, -1);
$values= substr($values, 0, -1);
$debug_values= substr($debug_values, 0, -1);
// finish SQL and debug SQL
$sql.= "$sql_fields) VALUES ($values)";
$debug_sql.= "$sql_fields) VALUES ($debug_values)";
if($debug === true) {
$return = $debug_sql;
}
else {
if(!$this->query($sql, $fields)->error()) {
$return = true;
}
}
}
return $return;
}
// now change the insert call to look like this
die($this->_db->insert('dbo.users', $fields, true)); // <-- notice the true parameter
/**
* Use the output to directly run the SQL from the MSSQL admin console or whatever they call it and it will provide a much more useful error description
*/

Categories