php pdo prepared statment and transitions - php

I am trying to write a function that is supposed to receive any MySQL statement and apply it,
The basic idea is not to repeat needed code to write to Database, well what is needed to connect to Database is creating new PDO object, starting a transaction and preparing a statement, binding values to it, executing it,
so every time I want to access the Database I don't have to repeat these steps,
Here is a function that does that :
==============================================================================================
protected function applyQuery($statement, $bindparameters , &$values , $selectStatement, &$result){
try{
$dbh = DataBase::setConnection();// new PDO("MySQL= .....");
$dbh->beginTransaction();
$stmt = $dbh->prepare($statement);
if($bindparameters == true){
foreach($values as $key => $value){
$stmt->bindValue($key, $value);
}
}
$stmt->execute();
$dbh->commit();
if($selectStatement == TRUE){
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}catch (PDOException $e){
$dbh->rollBack();
throw DataBase::$Errors[0];
}
}
============================================================================================
$statement = the desired statement (e.g 'SELECT * from users WHERE username = :username')///
$bindparameters = do we need to bind values (in this examples yes) so its value TRUE///
&$values = array by reference in this case equals = (':username' => 'User');///
$selectStatement = tells if using SELECT in statement ,in this case TRUE///
$result = array by reference in this case the final fetch result will be stored in it///
so in this example we get the following call to the function :
applyQuery('SELECT * from users WHERE username = :username', TRUE ,array(':username' => 'User') , TRUE , result )
My question is : will this code work ?
is the logical sequence of what it does and should do make sense ?
whats the difference between $stmt->execute and $dbh->commit ?
is omitting any line will cause failure to achieve the desired result
Please understand that I did lookup what is PDO and read a lot but unable to answer these questions!

Related

How do you stop injection in this PHP/PDO

So I have look at so many post, web sites and video and now I am so confused! I can't seem to get it right.
How do you stop injection in this PHP/PDO. I have this code that works, but it allows injection.
//*THIS WORKS BUT ALLOWS INJECTION
//*
//The variable $word comes from another php file where the search is created.
public function getAllCards($word) {
$sql = "SELECT * FROM carddbtable WHERE businessNameDB='".$word."'";
foreach ($this->conn->query($sql) as $row) {
echo json_encode($row)."<br>"."<br>";
}
$db = null;
}
With this new code I am trying to remove the variable "$word" from the "SELECT * FROM " statement
to stop the injection and add the "prepare" and the error checking and the "execute" statement, but I can't get it right. How would I do this? FYI this is a GoDaddy shared server.
//Getting the search "word" from the GetCards.php
public function getAllCards($word) {
//Empty var to store all returned info from db
$returnArray = array();
// sql statement to be executed
$sql = "SELECT * FROM carddbtable WHERE businessNameDB=':word";
// prepare to be executed
$statement = $this->conn->prepare($sql);
// error occurred
if (!$statement) {
throw new Exception($statement->error);
}
// execute statement
$statement->execute( :word => '$word' );
//run the query
foreach ($this->conn->query($statement) as $row) {
echo json_encode($row)."<br>"."<br>";
}
// store all appended $rows in $returnArray to be sent to app
$returnArray[] = $row;
}
You've almost got it. PDO, like many database drivers, will be responsible for all of the escaping, so just leave the placeholder as plain as possible:
$sql = "SELECT * FROM carddbtable WHERE businessNameDB=:word";
No ' necessary there.
Now when you execute() a PDO statement you get a result which you need to capture into a variable:
$res = $statement->execute([ 'word' => $word ]);
As Ibu and chris85 point out the '$word' part is also incorrect. Avoid quoting single variables, it's not only pointless, it can cause trouble, like here where you're binding to literally dollar-sign word, not the value in question. This goes doubly for "$word".
Then you fetch from that. Right now you're calling query() on the statement, which is incorrect.
Another thing to note is kicking the habit of making throw-away variables like $sql as these are just junk. Instead pass the argument directly:
$statement = $this->conn->prepare("SELECT * FROM carddbtable WHERE businessNameDB=:word");
This avoids accidentally mixing up $sql3 with $sql8 if you're juggling a bunch of these things.
This is what i have now.
//Getting the search "word" from the GetCards.php
public function getAllCards($word) {
//Empty var to store all returned info from db
$returnArray = array();
// prepare to be executed sql statement to be executed if not entered word
$statement = $this->conn->prepare("SELECT * FROM carddbtable WHERE businessNameDB=:word");
// error occurred
// if (!$statement) {
// throw new Exception($statement->error);
// }
// execute statement
$res = $statement->execute([ 'word' => $word ]);
//run the query
foreach ($this->conn->query($res) as $row) {
echo json_encode($row)."<br>"."<br>";
}
// store all appended $rows in $returnArray to be sent to app
$returnArray[] = $row;
}
I got this working
//*FUNCTION TO GET CARD FROM SEARCH WORD CALLED FROM GetCards.php
public function getAllCards($word) {
//Connect to db using the PDO not PHP
$db = new PDO('mysql:host=localhost;dbname=xxxx', 'xxxx', 'xxxx');
//Here we prepare the SELECT statement from the search word place holder :word
$sql = $db->prepare('SELECT * FROM carddbtable WHERE businessNameDB=:word');
//We execute the $sql with the search word variable"$word"
$sql->execute([':word' => $word]);
//Looping through the results
foreach ($sql as $row)
//Print to screen
echo json_encode($row). "<br>"."<br>";
}

PHP bind in for loop

I have rewritten the PDO fuction following advise from my previous question at PHP function/procedure to bind question marks dynamically
The problem I have is the result set returned is empty. The SQL query is correct int he sense that, when I run it manually, it does return data.
My suspicion is that the binding in the for loop is incorrect.
Could I please request guidance on
1) How to bind data in a for loop with question marks?
2) How to bind LIKE cases if the way I'm doing now is incorrect.
sample_sql_1="select f_name, age, address from table1 where l_name=? and dob >= ? and cty =?"
sample_sql_2="select * from table2 where cty LIKE ?"
$locn= "'" . $location . "%'";
pdo_db_query($sql_run,array(':empname'), array($locn));
function pdo_db_query($query, $bindnames = array(), $bindvals = array()) {
try {
# MySQL with PDO_MYSQL
$DBH = new DbConn();
$DBH->query($query);
foreach ($bindnames as $key => &$bindname) {
$DBH->bind( $bindname,$bindvals[$key]); // bind the value to the statement
}
$result=$DBH->resultset();
if($result){
var_dump($result);
}
# Close the connection
$DBH->CloseConnection();
} catch (PDOException $e) {
echo $e->getMessage();
var_dump($e->getMessage());
}
}
Here's the resultset function
public function resultset() {
$this->execute();
return $this->stmt->fetchAll(PDO::FETCH_ASSOC);
}
I got to the end of this post before I realized $DBH was actually an instance of a custom class. I'm leaving everything here for now in case I'm wrong and any of it helps.
BUT
I think you only problem is in the for loop
foreach ($bindnames as $key => &$bindname) should be foreach ($bindnames as $key => $bindname).
Original Answer
Have you tried a simpler example. It looks like you are doing many things incorrectly.
$DBH->query($query);
If you want to bind params to this later you should be preparing: $sth = $DBH->prepare($query);
foreach ($bindnames as $key => &$bindname) {
Are you sure that's doing what you think. I see no reason to us &$bindname it should be $bindname.
$DBH->bind( $bindname,$bindvals[$key]);
The function is actually PDOStatement::bindParam(), so you should be calling $sth->bindParam($bindname,$bindvals[$key]);. where $sth is the return value from $DBH->prepare($query);
$result=$DBH->resultset();
I need to understand the context of the resultset method better. Are you extending PDO or something?
Had to change
$locn= "'" . $location . "%'";
TO
$locn= $location . "%";
I realized that with binding, there is no need for single quote.

Database abstraction layer ontop of mysqli

when trying to build robust database code (table locking, transactions, etc) i am always annoyed by the mass of code that needs to be done.
For example a transaction out of two prepared statements where i want to delete a user and update something about him in an "actions" table:
Lock Table users, actions
Start a transaction (autocommit false)
Make a prepared statement for the deletion of a user
Check if statement is != false (cause it could have already failed at 3.
Bind param
Check errorState != "00000" on the statement (can also have failed at binding params)
execute statement
Check errorState != "00000" on the statement (can also have failed at executing)
get Result of statement
Close statement
Make a new prepared statement for update actions
Check if statement != false
bind params
check statement's errorState
execute
check statement's errorState
get result
close statement
check overall transaction state, if valid commit, if not valid rollback
unlock tables
set autocommit back to true
This is how i do it (maybe im doing it wrong?). And if i do it that way its a lot of work and annoying. So i thought automateing that stuff.
What i want is something like this:
$DB->startTransaction();
$DB->query($query);
$DB->query($query2);
$DB->query($query3);
$DB->endTransaction();
And internally the database abstraction layer ontop of mysqli will take care of table locking, prepared statements and transactions itself. Shouldn't we be able to automate this?
This is one of my attempts:
public function query($query, $table, $params = null) {
if($params == null) {
$this->connection->query("LOCK TABLES $table WRITE");
$query = str_replace("!", $table, $query);
$result = $this->connection->query($query);
$this->connection->query("UNLOCK TABLES");
return $result;
}
else {
if (!$this->checkParams($query, $params)) {
return false;
}
$this->connection->query("LOCK TABLES $table WRITE");
$query = str_replace("!", $table, $query);
$stmt = $this->connection->prepare($query);
if ($stmt != false) {
$typesString = "";
foreach ($params as $param) {
if (is_numeric($param)) {
$typesString .= "i";
} else if (is_double($param)) {
$typesString .= "d";
} else {
$typesString .= "s";
}
}
$finalParamArray = array($typesString);
$finalParamArray = array_merge($finalParamArray, $params);
call_user_func_array(array($stmt, "bind_param"), $this->ref($finalParamArray));
$this->checkStatement($stmt);
$stmt->execute();
$this->checkStatement($stmt);
$result = $stmt->get_result();
$stmt->close();
$this->connection->query("UNLOCK TABLES");
return $result;
}
$this->query("UNLOCK TABLES");
return false;
}
}
This would be callable like this:
$DB->query("DELETE FROM ! WHERE userID =?", "Users", array($userID));
I am however not feeling confident about this. I googled a bit and didn't find something like i want. So my question now is: Is something like i want actually possible (well it should be)? Am i doing it wrong?
EDIT:
I also have 2 other attempts of doing this, which look MUCH MORE complicated (300+ lines of code). I can post them as well, if you want. I am still however not satisfied with them and not confident if this is actually correct!
You are right there should be an easier way of doing this, and you are also correct to say that we need an abstraction layer on top of mysqli. It is not designed to be used on its own.
You do not need so many steps. In particular, you do not need to check the return code of each method. That should already eliminate 6 or more of your steps. You do not need to close a statement either.
There's no need to specify the type when binding. Just use string type all the time. Other types come in handy very rarely, almost never.
Some time ago I posted an example of what an abstraction layer on top of mysqli could look like.
class DBClass extends mysqli {
public function __construct(
$host = null,
$username = null,
$passwd = null,
$dbname = null,
$port = null,
$socket = null
) {
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
parent::__construct($host, $username, $passwd, $dbname, $port, $socket);
$this->set_charset('utf8mb4');
}
public function safeQuery(string $sql, array $params = []): ?array {
$stmt = $this->prepare($sql);
if ($params) {
$stmt->bind_param(str_repeat("s", count($params)), ...$params);
}
$stmt->execute();
if ($result = $stmt->get_result()) {
return $result->fetch_all(MYSQLI_BOTH);
}
return null;
}
}
This is far from perfect, but it shows the main idea. You can wrap a prepared statement in one single method. Simple prepare/bind/execute/get_result. Nothing more. It works with and without parameters.
In the constructor the 3 mandatory steps to opening a connection: switching error reporting, creating instance of mysqli and setting the correct charset.
If you want transactions, then you can use mysqli's begin_transaction() and commit(). They are simple enough and do not require abstraction.
I do not know why you feel you need to lock tables, but again this is a simple SQL statement and doesn't need to be abstracted.
$db = new DBClass('localhost', 'user', 'pass', 'test');
$db->safeQuery('LOCK TABLES users WRITE');
$db->begin_transaction();
$db->safeQuery('DELETE FROM users WHERE userID =?', [$userID]);
$db->safeQuery('DELETE FROM otherTable WHERE userID =?', [$userID2]);
$db->commit();
$db->safeQuery('UNLOCK TABLES');

php prepared statement and transictions , function

I am trying to write a function that is supposed to receive any MySQL statement and apply it,
The basic idea is not to repeat needed code to write to Database, well what is needed to connect to Database is creating new PDO object, starting a transaction and preparing a statement, binding values to it, executing it,
so every time I want to access the Database I don't have to repeat these steps,
Here is a function that does that :
==============================================================================================
protected function applyQuery($statement, $bindparameters , &$values , $selectStatement, &$result){
try{
$dbh = DataBase::setConnection();// new PDO("MySQL= .....");
$dbh->beginTransaction();
$stmt = $dbh->prepare($statement);
if($bindparameters == true){
foreach($values as $key => $value){
$stmt->bindValue($key, $value);
}
}
$stmt->execute();
$dbh->commit();
if($selectStatement == TRUE){
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}catch (PDOException $e){
$dbh->rollBack();
throw DataBase::$Errors[0];
}
}
============================================================================================
$statement = the desired statement (e.g 'SELECT * from users WHERE username = :username')
$bindparameters = do we need to bind values (in this examples yes) so its value TRUE
&$values = array by reference in this case equals = (':username' => 'User');
$selectStatement = tells if using SELECT in statement ,in this case TRUE
$result = array by reference in this case the final fetch result will be stored in it
so in this example we get the following call to the function :
applyQuery('SELECT * from users WHERE username = :username', TRUE ,
array(':username' => 'User') , TRUE , result )
My question is : will this code work ? is the logical sequence of what it does and should do make sense ? whats the difference between $stmt->execute and $dbh->commit ? is omitting any line will cause failure to achieve the desired result
Please understand that I did lookup what is PDO and read a lot but unable to answer these questions!

How to retrieve information using PDO method [duplicate]

This question already has answers here:
How to fetch a row with PDO
(2 answers)
Closed 8 years ago.
I am building a login script using the PDO method. I would like to register some sessions from the user account. Every time I try to pull information from the database I get blank. I am new to PDO. Here is what I have so far:
try{
$conn = new PDO(...........);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$conn->exec("SET CHARACTER SET utf8");
$sql = "SELECT * FROM $table WHERE USER = ':user' AND pass = ':pass'";
$UpdateSql = $conn->prepare($sql);
$UpdateSql->bindValue(':user',$user,PDO::PARAM_STR);
$UpdateSql->bindValue(':pass',$pass,PDO::PARAM_STR);
$results = $UpdateSql->execute();
/*Here is where the error comes in
foreach($results as $row)
{
$_SESSION['account'] = $row['account'];
}
*/
/*I also tried this
foreach($UpdateSql as $row)
{
$_SESSION['account'] = $row['account'];
}
*/
}catch(PDOException $e){
echo $e->getMessage();
}
When I tried the first foreach() I received an error Warning: Invalid argument supplied for foreach() in C:\xampp\htdocs\drug_center\dc_login.php on line 26 and the second foreach() gave me an empty result. Can someone please explain to me where I am making my mistake?
You'll be needing the $UpdateSql->fetchAll() method. This will give you a result set which can be traversed.
Secondly you'll be specyfing the parameters trough bindValue, so you don't need the ' around the values.
$table = (in_array($table, array('table1','table2')) ? $table : false;
//make sure you put some input-validation on the $table variabel!!
if($table){
$sql = "SELECT * FROM $table WHERE user = :user AND pass = :pass";
//lose the ' around the :pass
$UpdateSql = $conn->prepare($sql);
$UpdateSql->bindValue(':user',$user,PDO::PARAM_STR);
$UpdateSql->bindValue(':pass',$pass,PDO::PARAM_STR);
$UpdateSql->execute();
foreach($UpdateSql->fetchAll() AS $row){
// do some magic
}
}else{
throw new Exception("Error: table not allowed", 1);
}
Execute is to run a stored procedure on a SQL Server, you can
$results = $UpdateSql->fetchAll($sql);
foreach ($results as $row)
//stuff
}
The function prepare() returns an object of PDOStatement class. As it says on the PHP manual, PDOStatement::execute() does not return results, it returns a boolean instead to indicate if the query was successful or not. You need to use the fetch commands the PDOStatement has.

Categories