Using PHP PDO, unusual behavior with rowCount and catching exceptions - php

I'm getting some unusual behavior when using PHP/PDO and doing an INSERT. Here is some sample code:
foreach ($all_users as $this_user) {
try {
$stmt = $db->prepare('INSERT INTO fav_colors (name, color, whattime) VALUES (:name, :color, :whattime)');
$stmt->bindValue(':name', $this_user[name]);
$stmt->bindValue(':color', $this_user[color]);
$stmt->bindValue(':whattime', $this_user[time]);
$stmt->execute();
$count = $stmt->rowCount();
} catch(PDOException $e) { catchMySQLerror($e->getMessage()); }
if ($count == 1) {
echo "Successful write to table";
} elseif ($count == 0) {
echo "ERROR writing row to table";
}
}
Lets say I have 10 names/colors to insert and the times are DATETIME format (2016-01-01 12:00:00). I have a loop around this code and after the code I check $count to see if the insert worked OK.
Here is my problem. One of the 10 inserts, for whatever reason the $write_time was blank. mySQL generated an error (can't be null). My custom function catchMySQLerror writes the error to a table and e-mails me. It did, but $count was still 1 for this entry and according to it, everything was fine even though the record did not insert.
So is my problem with the try/catch I am using here to catch the exception? How can I add something where I can better handle the error within the code? I know I can add more lines after the catchMySQLerror line and work with the error that way. That is what I have done. But I would like to handle the error using an if/else and with the approach I just mentioned, I can only handle the error... not if it was success.
UPDATE: I edited the code to show how I have been using $count

If ->execute() throws an exception, $count = $stmt->rowCount(); isn't executed and therefore $count keeps the value of the previous iteration (if there was one).
If you rely on that value, you have to (re-)initilize it at the beginning of every iteration.

this is how it have to be done
foreach ($all_users as $this_user) {
try {
$stmt = $db->prepare('INSERT INTO fav_colors (name, color, whattime) VALUES (:name, :color, :whattime)');
$stmt->bindValue(':name', $this_user[name]);
$stmt->bindValue(':color', $this_user[color]);
$stmt->bindValue(':whattime', $this_user[time]);
$stmt->execute();
} catch(PDOException $e) {
catchMySQLerror($e);
echo "ERROR writing row to table";
}
}

Related

How to debug PDO script?

I have a PHP script that is executed daily by my server thanks to cron.
This script contains PDO queries to add, edit, and delete data from my MySQL database.
The script does not work as expected, especially the last part of the query which is supposed to remove some rows:
$stmt = $conn->prepare("DELETE FROM `mkgaction` WHERE score IS NULL");
$stmt->execute();
if($stmt->execute()) {
echo "delete succeeded<br>";
} else {
echo "delete failed<br>";
}
When executed manually via PHPMyAdmin, every query works fine. When executed via this script it does not work despite the message showing "delete succeeded".
I suppose the best way to understand what actually happens is to read the response from the database, but I don't know how to do that.
Would you help me? :-)
Thanks
Always check the return value of prepare() and execute(). They return the boolean value false if there's a problem.
Then you should check the specific error and report that error to help debugging.
$stmt = $conn->prepare("DELETE FROM `mkgaction` WHERE score IS NULL");
if ($stmt === false) {
die(print_r($conn->errorInfo(), true));
}
$ok = $stmt->execute();
if ($ok === false) {
die(print_r($stmt->errorInfo(), true));
}
echo "delete succeeded<br>";
Admittedly, checking every call gets to be a lot of repetitive code. An alternative is to enable exceptions, if you're comfortable writing code to handle exceptions. See https://www.php.net/manual/en/pdo.error-handling.php

How to return number rows affected on multiple inserts with Mysqli Php?

I have a function which inserts multiple rows using the MySqli library with prepared statements. The inserts works great, the problem is the build in $stmt->affected_rows method always returns the number of affected rows as 1.
Now to move around the affected row issue I created a counter which counts each executed statement. This solution is accurate. But I enjoy using built in methods and functions, so why is the $stmt->affected_rows always returning one, even though I inserted multiple rows? Is my code defective in some way or form? Maybe there is a pure Sql solution.
Here is my code:
try {
$query = "INSERT INTO dryenrolltb(enroll_id,id_entity,bin_type,tara_weight,dtetime_created,enrollprint_status) VALUES(?,?,?,?,?,?)";
$stmt = $db->prepare($query);
$stmt->bind_param('iiidsi', $enroll,$ent,$bin,$tara,$dte_create,$enr_status);
$result['rows']['rowerrors'] = array();
$result['rows']['rowsaffected'] = [];
$cnt = 0;
foreach ($arr as $value) {
$enroll = $value['enroll'];
$ent = $value['entid'];
$bin = $value['bin_t'];
$tara = $value['tara'];
$dte_create = $value['dtecreat'];
$enr_status = $value['enr_status'];
if($stmt->execute()) {
$cnt++;
} else {
array_push($result['rows']['rowerrors'],$value['enroll']);
}
}
if ($stmt->affected_rows > 0) {
echo "Affectionately yours";
array_push($result['rows']['rowsaffected'], $stmt->affected_rows);
array_push($result['rows']['rowsaffected'], $cnt);
return $result;
} else {
return false;
}
} catch (Exception $e) {
echo "Danger exception caught";
return false;
}
Can someone please give me a clue on why the $stmt->affected_rows always returns one on multiple inserts?
No. It seems like MySQLi statement class has no way of storing a running total of affected rows. After I thought about it, it makes total sense. Let me explain.
Every time you execute the statement it will affect a given number of rows. In your case you have a simple INSERT statement, which will add records one by one. Therefore, each time you call execute() the affected_rows value is one.
The query could be something different. For example INSERT INTO ... SELECT or UPDATE could affect multiple rows.
You could also have INSERT INTO ... ON DUPLICATE KEY UPDATE. If the key exists in DB, then you are not inserting anything. If the values are the same, you are not even updating anything. The affected rows, could be 0 or more.
The reason why it would be unwise for the statement to keep a running total of the affected rows is that each execution affects certain rows, irrespective of the previous executions. They could be even the same records. Consider the following example:
$stmt = $mysqli->prepare('UPDATE users SET username=? WHERE id=?');
$stmt->bind_param('si', $name, $id);
$id = 102;
$name = 'Affected rows 1';
$stmt->execute();
echo $stmt->affected_rows; // 1
$name = 'Affected rows 2';
$stmt->execute();
echo $stmt->affected_rows; // 1
Both update statements updated the same row. If mysqli kept a running total it would report 2 affected rows, but in reality only 1 row was changed. If the number was summed, you would be losing information.
So, for your simple scenario, it is fine to keep the total on your own, for example by summing up the $stmt->affected_rows after each execution. Anything more than that, it would probably not make much sense.

mysqli_multi_query: need to check single result?

I'm using this code to insert a record with a unique id and then return the id-string just created:
$sql ="set #id=UUID();";
$sql .="INSERT INTO `items` (`id`,`parent`, `text`, `type`) VALUES(#id,'".$parent."', '".$text."', '".$type."');";
$sql .="select #id;";
if (mysqli_multi_query($conn, $sql)) {
while (mysqli_more_results($conn)) {
mysqli_use_result($conn);
mysqli_next_result($conn);
}//while
$result = array('id' => mysqli_store_result($conn)->fetch_row()[0]);
}//if
If everything works as it should, the three queries should return:
1/true (I guess)
1/true
object
I never used this function before and I was wondering: what happens if the insert query fails?
The third query will still be executed?
And in that case, how can I check the result of the second query?
Edit:
Or in general:
having a set of 10 queries, in case of failure how can I check which one has failed?
After some test...
Let's assume we have the following 3 queries:
$sql ="INSERT INTO `items` (`id`,`parent`) VALUES ('id',WRONG_NO_QUOTES);";
$sql .="INSERT INTO `items` (`id`,`parent`) VALUES ('id','right2');";
$sql .="INSERT INTO `items` (`id`,`parent`) VALUES ('id','right3');";
Using the code provided by the manual...
if (mysqli_multi_query($conn,$sql)) {
do {
/* store first result set */
if ($result = mysqli_store_result($conn)) {
while ($row = $result->fetch_row()) {
printf("%s\n", $row[0]);
}
$result->free();
}
/* print divider */
if (mysqli_more_results($conn)) {
printf("-----------------\n");
}
} while (mysqli_next_result($conn));
}
//...plus this....
if(mysqli_errno($conn)){
echo 'mysqli_errno:';
var_dump(mysqli_errno($conn));
echo'<br>';
}
...the output is:
mysqli_errno:
[...]test.php:39:int 1146
...and there are no changes to the database.
Conclusion:
When an error occours, the next queries are NOT executed.
AND the error can be easly catched at the end of the do/while loop.
EDIT
The code above generates a strict standards notice.
I edited it to avoid the notice and get a (basic) backtrace of the error:
$n=1;
$i=1;
if (mysqli_multi_query($conn,$sql)) {
do {
/* store first result set */
if ($result = mysqli_store_result($conn)) {
while ($row = $result->fetch_row()) {
echo $row[0];
}
$result->free();
}
/* print divider */
if (mysqli_more_results($conn)) {
echo "<hr>";
$n++;
mysqli_next_result($conn);
}else{$i=0;}
} while ($i>0);
}
if(mysqli_errno($conn)){
echo 'mysqli error on query number '.$n;
var_dump(mysqli_errno($conn));
}
Since my actual problem was to avoid competition between queries and get the right identifier (check: this question) I realized there is a simpler (and maybe better) way to reach the desired result (here using pdo extension):
$db = new PDO ("mysql:host=$hostname;dbname=$dbname", $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//No user defined variable here inside (safe)
$identifier=db->query("SELECT UUID();")->fetch()[0];
Having the identifier stored into a PHP variable, then I can treat every following query separately using pdo and prepared statements.

PHP: How do I get my IF statement to work with PDO select?

I want my below PDO select to work with the bottom two IF statements?
The first IF I just want to make sure there is no error.
The second IF I want to check how many rows it returns. I know that this number of rows == 0 will not work.
Is there a way to do that?
try {
$conn = new PDO('mysql:host=localhost;dbname=zs', 'zs', 'rlkj08sfSsdf');
$conn ->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo $e->getMessage();
die();
}
$stmt = $conn->prepare("SELECT * FROM zip WHERE zip_code =:zip1");
$stmt->bindValue(':zip1', $_POST[zipcode], PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if($rows = "") {
echo "<p><strong>There was a database error attempting to retrieve your ZIP Code.</strong></p>\n";
}
if(number of rows == 0) {
echo "<p><strong>No database match for provided ZIP Code.</strong> Please enter a new ZIP Code.</p>\n";
}
You're interested only in whether there are records containing a particular value. It makes no sense to select everything and count the records in PHP. It's a waste of resources. Imagine what happens if there's a million records.
Solution you're after is to simply ask your database about the COUNT of rows containing a particular value. Your code should be quite simple:
$stmt = $conn->prepare("SELECT COUNT(*) AS num_rows FROM zip WHERE zip_code = :zip");
$stmt->bindValue(':zip', $_POST['zipcode'], PDO::PARAM_INT);
$stmt->execute();
$count = (int)$stmt->fetchColumn();
if($count)
{
echo "Success";
}
else
{
echo "Bummer";
}
Notes:
if successful, the above query will always return 1 row with 1 column, named num_rows which will be 0 for no matching records or an integer larger than 0 if there are records. If you use MySQL native driver with PHP, PHP will correctly represent this value as integer internally. I deliberately put typecasting in, you can remove it (the (int) part) if you have MySQL ND.
if something goes wrong during query execution, an exception will be thrown. The snippet doesn't cover that. You correctly set PDO in exception mode, and along with using bindValue instead of bindParam, this implies you did your research right and you're using PDO correctly which means that error handling should be implemented easily by you in this particular case.

MYSQL result not returning correctly

So I have a piece of code that will check if you have followed a user or not. And basically let you follow them if you haven't. So here it is
if($_SESSION['loggedIn'] == true){
$result = $con->prepare("SELECT * FROM followers WHERE follow_from = :username AND follow_to = :post_id");
$result->bindParam(':username', $username);
$result->bindParam(':post_id', $follower);
$result->execute();
$reprint = $result->fetchAll(PDO::FETCH_ASSOC);
}
print_r($reprint);
if($reprint < 1){
$stmt = $con->prepare("INSERT INTO followers (follow_from, follow_to) VALUES (:ff, :ft)");
$stmt->bindValue(':ff', $follower, PDO::PARAM_STR);
$stmt->bindValue(':ft', $username, PDO::PARAM_STR);
$stmt->execute();
}
else{
echo 'Error';
exit();
}
//Display follower
$stmt1 = $con->prepare("SELECT COUNT(*) AS count FROM followers WHERE follow_to = :username");
$stmt1->bindValue(':username', $username, PDO::PARAM_STR);
$stmt1->execute();
$likes = $stmt1->fetchAll(PDO::FETCH_ASSOC);
print_r($likes);
So when I run it once. I get the else statement echoed. My question is why does this happen? In the database I have no record, so I'd expect it to go in once. I get no errors at all. loggedIn is true. And variables are being passed through successfully.
Any ideas?
You're misusing the result you get from fetchAll(). It's an associative array, not a scalar value. It could, as you've probably guessed, be empty.
But, more significantly than that, your code has a potential race condition. What happens if two different sessions are trying to set this same followers row? (Admittedly, in a small system that is unlikely, but in a large system it might happen).
What you actually do is just the INSERT operation. If your followers row has a unique key on the (follow_from,follow_to) columns, then, if that row is already there you'll get a 'Duplicate entry' error on the INSERT. Otherwise it will just happen. You can just ignore the 'Duplicate entry' error, because all you want is for that row to make it into that table.
So your code would go like this:
$stmt = $con->prepare("INSERT
INTO followers (follow_from, follow_to)
VALUES (:ff, :ft)");
$stmt->bindValue(':ff', $follower, PDO::PARAM_STR);
$stmt->bindValue(':ft', $username, PDO::PARAM_STR);
$result = $stmt->execute();
if ($result) {
/* this follow pair was successfully added */
} else {
/* MySQL may return the error 'Duplicate entry' */
if (false == stripos($stmt->errorCode,'Duplicate')){
echo 'Something failed in the insert: ' . '$stmt->errorCode';
}
else {
/* this follow pair was already in your table */
}
}
Pro tip: Don't use SELECT * in software; it can mess up query optimization; it often sends more data than you need from the server to your program, and it makes your program less resilient if your change your table definitions.
Pro tip: If you must count rows matching a particular WHERE statement, use COUNT() rather than fetching the rows and counting them in the client. What if you get a million rows?
You'd want to use count($reprint) other that a direct comparison. $reprint is an array, not a number
if(count($reprint) < 1)
{
$stmt = $con->prepare("INSERT INTO followers (follow_from, follow_to) VALUES (:ff, :ft)");
$stmt->bindValue(':ff', $follower, PDO::PARAM_STR);
$stmt->bindValue(':ft', $username, PDO::PARAM_STR);
$stmt->execute();
}
else
{
echo 'Error';
exit();
}
PDOStatement::fetchAll
PDOStatement::fetchAll — Returns an array containing all of the result set rows
If you check the size of the array then you would actually know if something happened.
Using proper error handling can tell you if something's failing deep down:
try
{
...
}
catch (PDOException $e)
{
echo $e->getMessage();
}
You will need to enable PDO error-displaying:
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
If checking the size of the array doesn't do it and you get no errors then it's simply some logic error.
Most likely the logic error is that
$reprint = $result->fetchAll(PDO::FETCH_ASSOC);
doesn't get executed (wrapping the error handling around that should tell you why), so
$reprint = $result->fetchAll(PDO::FETCH_ASSOC);
isn't given a proper value, meaning you'll always hit the else statement.
Edit
Your original problem was that "So when I run it once. I get the else statement echoed. [...] In the database I have no record" but now you're saying "It adds the record, but doesn't limit it to me one".
Can you be more clear about the actual, current, problem?

Categories