Are prepare() and transactions mutually exclusive? I've got a lot of queries that I build and then execute, so it sounds like using a transaction is what I want; but I read on the prepare.statment page that using the bindParam method eliminates SQL-injection. Is there some way to do both?
Here's an example of the code I have right now (which may or may not be correct):
$dbhost=FOO;
$dbuser=FOOBAR;
$dbpass=RABOOF;
$options=array(STUFF);
$dbh = new PDO("mysql:host=$dbhost", $dbuser, $dbpass, $options);
// I know this ^ works
$dbh->beginTransaction();
$record_data = $dbh->prepare("UPDATE $db.$tbl SET :column=:value WHERE `key` = :key;");
function record_data($q,$a,$k){
$record_data->bindParam(':column', $q);
$record_data->bindParam(':value', $a);
$record_data->bindParam(':key', $k);
$record_data->execute();
}
// $pairs is an array with ~50 objects/rows
foreach($pairs as $pair){
list($qstn , $ans) = explode('=', $pair);
switch($qstn){
case 1: if(something) record_data($qstn,$ans,$key); break;
case 2: if(something) record_data($qstn,$ans,$key); break;
case 3: if(something) record_data($qstn,$ans,$key); break;
// more
default: record_data($qstn,$ans,$key); break;
}
}
$dbh->commit();
When I tried out the full code, I got No connection could be made because the target machine actively refused it. Usually I see a message like that when my connection info is wrong (or the account isn't set up properly/as I expect). But I tested the PDO connection separately and it worked fine. So I probably did something else wrong.
EDIT: Are variables allowed in prepare()?
EDIT 2: I added try{} around the $dbh = PDO(…) and added echo "connected" at the end of the try (and did the catch bit), and it echo'd "connected", so it is connecting. But after "connected" it prints that error message, so the issue is happening after a successful connection.
EDIT 3: I added
$dbRS = $dbh->query("SELECT * FROM `database`.`table`;");
$row = empty($dbRS) ? false : $dbRS->fetch(PDO::FETCH_ASSOC);
print_r($row);
and it printed the first row of the table, so for sure it's connecting.
"Are mutually exclusive?": No, as you show, is a kind of "function declaration", and transaction is like a (OS) process where the function runs.
"Are variables allowed?": I think you must start checking your PHP function record_data($q,$a,$k): there are a error. Try add global $record_data; at the begin of the function.
General comments: the main advantage of PDO is to capture errors (by PHP error line or returning SQL error messages) for each single SQL statment.
See pdo.begintransaction, pdo.commit, pdo.rollback and pdo.error-handling.
Example:
$dbh->beginTransaction();
/* Do SQL */
$sth1 = $dbh->exec("CREATE TABLE xyz (..)");
$sth2 = record_data($qstn1,$ans1,$key1);
$sth2 = record_data($qstn2,$ans2,$key2);
/* Commit the changes */
$dbh->commit();
You are using vars that were not defined in the scope of the function. Simply use:
global $record_data;
as the first line in the function and it will work.
Related
I am iterating rows of a csv file.
On row 1, I call stored procedure (import_extended_data_sp) and it succeeds.
On row 2, the call fails with :
Strict Standards mysqli::next_result(): There is no next result set.
However, with the call being exactly the same as the first, I am struggling to see why ?
I have now hard coded test values as parameters, and checked that the Sproc has no issue with the same values being given twice.
It still fails on the second call !?
Am wondering if there is some nuance of mysqli, where I need to clear or reset something before making the second call ?
<?php include("cnn.php");?>
<?php include("fn_db.php");?>
# ... get csv file (skipped for brevity) #
while($row = fgetcsv($file_data))
{
$line = array_combine($head, $row);
# This call works on every loop - no issues
$id = placemark_to_db($mysqli,$v_header,$line['id_placemark'],$line['name'],$line['swim_type'],$line['latitude'],$line['longitude'],$line['description']);
# This next line only succeeds on first call, but fails on next while loop
$x = xtended_to_db($mysqli,'99','[{"xtra":"oo"}]');
}
** fn_db.php >> xtended_to_db**
function xtended_to_db($cn,$id,$jsonarray){
# procedure returns a rowcount in output parameter
$cn->multi_query( "CALL import_extended_data_sp($id,'$jsonarray',#out);select #out as _out");
$cn->next_result();
$rs=$cn->store_result();
$ret = $rs->fetch_object()->_out;
$rs->free();
return $ret;
}
cnn.php
<?php
$mysqli = new mysqli("xxx.xxx.xxx.xxx","mydb","pass","user");
// Check connection
if ($mysqli -> connect_errno) {
echo "Failed to connect to MySQL: " . $mysqli -> connect_error;
exit();
}
?>
The best way to fix this error is to avoid multi_query() altogether. While it might sound like a reasonable use case with stored procedures, the truth is this function is mostly useless and very dangerous. You can achieve the same result using the normal way with prepared statements.
function xtended_to_db(mysqli $cn, $id, $jsonarray) {
$stmt = $cn->prepare('CALL import_extended_data_sp(?,?,#out)');
$stmt->bind_param('ss', $id, $jsonarray);
$stmt->execute();
$stmt = $cn->prepare('select #out as _out');
$stmt->execute();
$rs = $stmt->get_result();
return $rs->fetch_object()->_out;
}
If you are stuborn and you want to keep on using multi_query() then you need to be more careful with how you fetch results. This function is extremely difficult to get right. I am not going to show you how to fix multi_query() as I consider it too dangerous with variable input.
One last note, you really should think about getting rid of stored procedures. They are cumbersome and offer pretty much no benefit. There definitely is a better way to achieve what you want rather than calling stored procedure from PHP, but without seeing its contents I can't give you better advice.
The below php code runs a query for which i get $res = true which means that the query should have run.
I checked the health of the PDO object also to make sure and found out that other queries get run correctly.
Also no exception were caught. Have run out of ways to solve this issue.
function insertPaypalSavedSanity($udi, $isPaypalSaved){
global $DBH;
try {
$sql = "INSERT INTO sanity(user_id, saved_paypal_used_previously) VALUES(:user_id,:sv) ON DUPLICATE KEY UPDATE saved_paypal_used_previously=:sv";
$sqlcon = $DBH->prepare($sql);
$res = $sqlcon->execute(array(
'user_id'=>$_SESSION['userID'],
'sv'=>$isPaypalSaved));//$res is true after executing the query
$x = 1;// I put the debugger at this point and run other queries with my PDO object($DBH)
} catch(Exception $e)
{
logger ("sanity queries",$e->getMessage());
}
}
This got solved by adding start transaction to beginning of statement and commit to the end of statement. I still have no clue as to why this worked.
On my site, we connect to several databases and we have a database handling class that stores the handles in a static array. 99.9% of the time, everything seems to work fine, but every now and then the handle fails silently with no error, and no results are returned. I've been able to recreate the error, then recreate the connection using the following syntax:
$db = new PDO("dblib:host=" . DB_HOST . ";dbname=" . DB_DEFAULT, DB_USER, DB_PASS);
Then, it returns results correctly. However, I would prefer to use the same handle for all databases, though, to preserve the extra overhead associated with creating a new connection to a database... or, at least, recreate the connection a minimum number of times.
Is there anyway to evaluate the PDO object to figure out why it's failing, so I can, hopefully, figure out what's going on?
EDIT:
I've been looking into this further, and profiled the DB while this error is occuring. PDO stops passing queries to the server after it hits certain queries. (In this case, it was an insert query that tests to see if a record already exists, and if it doesn't then it inserts it).
Make no mistake, this is a horrible and inelligant solution to my problem, but I copied and pasted (and universalized) what resolved it. Basically, when no results are returned, I select 'foo' from the database, if it comes back with no results, I rebuild the connection and attempt to execute the query again. I'm not accepting this as an answer, because it's pretty bad. But this code should better illustrate the problem I am, attempting, to fix.
/**
* Executes a line of sql with PDO.
*
* #param string $sql
* #param array $params
* #param bool
* #return array
*/
function execute($sql, $params = array(), $retry = false) {
//this call gets the database handle that is stored in a static array, so we can reuse the same connection
$db = db::getDB($this->_database);
if (get_class($db) != 'PDO') {
error('database handle wasn\'t created succesfully...');
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
$query = $db->prepare($sql);
$query->execute($params);
} catch (Exception $ex) {
var_dump($ex);
return false;
}
$results = $query->fetchALL(PDO::FETCH_ASSOC);
if(($retry == false) && (!$results)){
$newResults = $this->testAndFixResultsIfDBGoneAway($sql, $params);
if($newResults != false){
$results = $newResults;
}
}
return $results;
}
function isConnected(){
$db = db::getDB($this->_database);
$query = $db->prepare("SELECT 'foo' AS test");
$query->execute();
$results = $query->fetchAll(PDO::FETCH_ASSOC);
//we aren't connected if we get no response
if($results == array()){
return false;
}
//if there is a response, return true
return true;
}
function testAndFixResultsIfDBGoneAway($sql, $params){
if($this->isConnected()){
return false;
}
//recreates the db connection in the db connection class
db::connect($this->_database);
$results = $this->execute($sql, $params, true);
return $results;
}
I've noticed that my connection goes away, I can restart it successfully, then the next query can fail, and I can repeat this process many times, until the offending queries are done, then everything starts working normally, again.
I REALLY want to know how queries kill my database connection, so I can stop it from happening so I don't have to do a check after every query that returns no results.
Setting the PDO error-handling mode to use exceptions should make the cause immediately apparent so you don't have to hunt around and use a lot of debugging code.
$db = new PDO("dblib:host=" . DB_HOST . ";dbname=" . DB_DEFAULT, DB_USER, DB_PASS, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
However, you'll have to update your error-handling code [failed prepares, failed queries] to be aware of the Exceptions.
I have this problem that drives me crazy. All i want is just check a query for an error, if so display error, otherwise run the query.
I have the following almost (since it runs the insert query twice) working
[..]
$dbdata = new mySQLAccessData();
$db = new PDO($dbdata->hostname,$dbdata->username,$dbdata->password);
$defaults = new Defaults();
[..]
if(!$db->exec($sql)){
echo($defaults->throwError('MySql error',implode(":",$db->errorInfo())));
}else{
$db->exec($sql);
$defaults->writeLog($table,$db->lastInsertId(),'add');
}
I tried numerous things (amongst others the try(){}catch(){} method) but nothing worked except for the code above. It shows the error the way i want, and only when an error occurs, but runs the exec() twice...
Can someone bail me out?
Why do you want to execute the query again in the else part? Usually you just try to run the query and if errors happen, react on them.
[..]
$dbdata = new mySQLAccessData();
$db = new PDO($dbdata->hostname,$dbdata->username,$dbdata->password);
$defaults = new Defaults();
[..]
if(!$db->exec($sql)){
echo($defaults->throwError('MySql error',implode(":",$db->errorInfo())));
}else{
$defaults->writeLog($table,$db->lastInsertId(),'add');
}
As far as I know, there is no option to "test" a query before actually executing it.
If you want to see an exception thrown when an error occurs, just set the PDO error-mode (see also: Connections and Connection management):
$db = new PDO($dbdata->hostname,$dbdata->username,$dbdata->password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
This for example will make your code throw exceptions automatically. Probably exactly what you're looking for.
The actual issue with your code is doing exec twice. You don't need to:
$success = $db->exec($sql);
if (!$success) {
echo $defaults->throwError('MySql error', implode(":", $db->errorInfo()));
} else {
# do not exec *again* here.
$defaults->writeLog($table, $db->lastInsertId(), 'add');
}
I managed to run the following code to insert into my table on first try. Then, I deleted that row in PHPMyAdmin to test my code further. I also noticed that it didn't get deleted on the 1st try. Only after few try. This might be due to I didn't set the $pdoHandle to NULL after I'm done with the query.
Then, unfortunately I couldn't insert new row on subsequent run. I even tried to change the input value and to avail I was unable to insert new row. The following are my PHP codes:
public function CreateNewCustomer($userId,$password,$name,$email)
{
$userId = filter_var($userId,FILTER_SANITIZE_STRING);
$password = filter_var($password,FILTER_SANITIZE_STRING);
$password = sha1($password);
$name = filter_var($name,FILTER_SANITIZE_STRING);
$email = filter_var($email,FILTER_SANITIZE_EMAIL);
do{
$customerId = hexdec(bin2hex(openssl_random_pseudo_bytes(4,$isStrong)));
echo $customerId;
$result = $this->connObject->exec("SELECT COUNT(id) FROM customer_tbl WHERE id=$customerId");
var_dump($result);
}while($result>0);
$statement = $this->connObject->prepare("INSERT INTO customer_tbl (id,name,email) VALUES ($customerId,:name,:email)");
$result = $statement->execute(array(':name'=>$name,':email'=>$email ));
var_dump($result);
$statement = $this->connObject->prepare("INSERT INTO login_tbl (username,password,customer_id) VALUES (:userName,PASSWORD(:password),$customerId)");
$result = $statement->execute(array(':userName'=>$userId,':password'=>$password ));
var_dump($result);
}
I used the following code to access the above method.
function Test($userName,$password,$name,$email)
{
try
{
$dbConnect = new DbConnect();
$pdoHandle = $dbConnect->Connect();
$userAccess = new UserAccess($pdoHandle);
$userAccess->CreateNewCustomer($userName,$password,$name,$email);
}
catch(PDOException $e)
{
$pdoHandle = null;
var_dump($e);
}
$pdoHandle = null;
}
Test('tester','password','TestX','test#example.com');
The var_dump of results is always false.
Is there any problem with my codes or is it something wrong with the database?
UPDATE/SOLUTION:
I just read through the PHP document on PDO::exec() and one of the user contributed notes mentioned that you can't use any SELECT statements (even thou the above only returns the count value) and any statements which might return a rows. The return value of PDO::exec() is the number of affected rows (integer), so the PDOStatement::closeCursor() can't be used to solve it. Even when I set the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=>true, it still doesn't work.
So, don't use PDO::exec() for any SELECT. I changed my code to PDO::query() instead as below,
do{
$customerId = hexdec(bin2hex(openssl_random_pseudo_bytes(4,$isStrong)));
$statement = $this->connObject->query("SELECT COUNT(id) FROM customer_tbl WHERE id=$customerId");
$statement->execute();
}while($statement->fetchColumn(0)>0);
Hope this would be helpful to anyone looking for a solution with similar problem and always remember to read the PHP document first including the user contributions.
Maybe not the answer but here are some things that you can do if you cannto see an obvious error:
If execute returns false, you can get more information about the error that happened by:
$arr = $statement->errorInfo();
print_r($arr);
or you can set different error reporting modes (e.g. throw an exception instead of the defaultsilent mode):
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
This should help you to find the "real" error.
As it turned out (see comments below question), in this case the real error was:
"Cannot execute queries while other unbuffered queries are active.
Consider using PDOStatement::fetchAll(). Alternatively, if your code
is only ever going to run against mysql, you may enable query
buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY"
In this case you have 2 options:
you can set the option to use buffered queries
$dbh = new PDO(’mysql:host=localhost;dbname=test’, ‘root’, ” ,array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true))
or change your code and close an open cursor (may depend on the db driver you are using). You always should read the documentation which covers a lot of default problems.
Hope this helps.
I'm assuming the method is inside the UserAccess class and the connection you pass in is set to the local $this->connObject.
I suspect after you deleted the record, $customerId is being set to null in your interesting do-while loop with the select statement. If the id column in the DB is a non-null primary key field and you try to insert an explicit null it will fail.
Also, no need to keep setting your DB connection to null... this isn't C and connections aren't persistent (unless you explicitly declare them as such).