How would I go about getting PDO statements to generate a safe error message? I don't want the user to see the error message. I want them to get directed to a page that says a clean message, "Whoops something unexpected happened!". I would also like to log the errors in a database to review and catch errors others are generating.
I'm using PHP and MySQL.
I found that when you make your connection you can set your error handling like this.
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Anyone do anything like this before?
So this is just a suggestion as I have never tried this but after thinking about it a bit I think it would be an interesting option to explore. As I am fairly new to PHP & PDO I'm sure there are other and better ways.
Perhaps you could try using the try function of PHP and then instead of echo'ing (if failed) the PDOException you could run another function that prints it to a text file. Something like.
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
foreach($dbh->query('SELECT * from FOO') as $row) {
print_r($row);
}
$dbh = null;
} catch (PDOException $e) {
$strFileName = 'whatever.txt';
if(!is_writable($strFileName))
die('Change permisions to ' . $strFileName);
$handle = fopen($strFileName, 'a+');
fwrite($handle, "\r" . $e->getMessage() . "\r");
fclose($handle);
}
?>
This way you would avoid a DB connection (which is the problem I guess) but still save the error.
You would perhaps want to omit the echo'd text after die within the if statement.
I think it is better to write your logs to a file, instead of a database. Especially since you want to log PDO errors, which indicate something is wrong with your database connection.
You can show the user a nice error page by catching your errors. You can redirect your users to your error page then, in case something went wrong.
You have to understand that PDO do not generate a "safe" or "unsafe" error message. It does generate an error message. That's all. The rest is is the responsibility of site-wide PHP settings.
PDO is not the only source of errors. Why care of PDO errors only? Why not to handle ALL errors the same way?
Want errors logged? It's a matter of one PHP ini setting.
Want errors not to be displayed? It's a matter of one PHP ini setting.
Want generic error page to be shown? It's a matter of simple function that will handle all errors at once.
Everything can be done proper and straight way, without wrapping every statement into try catch. Without writing into log manually. Without even single additional line of code.
You need to set up PHP error handling, not PDO.
And of course, it makes absolutely no sense in trying to store a database error in the same database that failed you right now. Errors have to go into error log on a live server and on screen - on a local development PC.
Anyone do anything like this before?
Sure. Every single one of 1000000s sites in the world. The way described above.
Related
I've just finished refactoring a bunch of MySQL and MySQLi forms to PDO.
Everything seems to be working.
Now on to error handling.
In the MySQL / MySQLi code I had been using if statements to catch errors. Like this:
if (!$database_connection) {
// error handling here
}
Plain and simple.
But I can't get a similar set-up to work with PDO.
Here's the scenario:
I have a connection to a database that looks something like this:
$data_source_name = "mysql:host=$db_host;dbname=$db_name";
$database_connection = new PDO($data_source_name, $db_username, $db_password);
The execution looks something like this:
$stmt= $database_connection->prepare($sql);
$stmt->execute([$name, $email]);
I'm trying to set up a condition like the one described above:
if ( database connection fails ) {
// error handling
}
But this code doesn't work.
if ( !$database_connection ) {
// error handling
} else {
$stmt= $database_connection->prepare($sql);
$stmt->execute([$name, $email]);
}
This if construct worked in MySQL (now deprecated) and works in MySQLi, but not PDO.
I was originally trying to make this work using try-catch, as recommended in many Stack posts. But after more research it appears that this function is inappropriate for PDO Exceptions.
Any guidance and suggestions appreciated. Thanks.
It's a very common fallacy, that one needs a dedicated error handling code for PDO or Mysqli (or whatever else module for that matter). Least it should be even more specific, such as "Mysqli connection" handler, as it seems with your old mysqli code.
If you think of it, you don't really care whether it was exactly a database error that prevented the code form being executed correctly. There can be any other problem as well.
Admittedly, one hardly can expect any other problem from such a simple code but still, the code may grow, become more modular, perform more tasks - and therefore error out in any other part as well. Like, writing database credentials in the every file is a bit of waste. So it's natural to put them in a file and then just include it in the every other script that requires a database interaction. So this file may get corrupted which will has the same effect as a database error. And will need to be fixed as well.
Or, if you're handling only the connection error, the problem can happen during the query execution as well (especially in your case, as the way the query is executed it will error out even if a customer will simply enter fish'h'chips for example).
What you really care for is whether the data has been stored correctly (and probably whether emails were sent as well) or not, no matter what could be the possible failure. This is being the exact reason, why I wrote in the article this warning against wrapping some specific part of code in a try-catch in order to report this particular error. As error reporting handler must be common for the entire code.
Admittedly, the simplest exception handling method is simply wrapping the entire code in a try catch block where the most generic exception type, namely Throwable, must be checked for. Not very reliable but simplest.
The key here is to wrap the entire code, not just some random part of it. But one shouldn't forget to set the exception mode for PDO, in order let the query execution errors to be caught in this block as well.
<?php
try {
require 'pdo.php'
...
$sql = "INSERT INTO other_choices (order,foods) VALUES (?,?)";
...
$stmt= $db_connection->prepare($sql);
$stmt->execute([$order, $foods]);
// send emails, etc
} catch (Throwable $e) {
// do your handling here
}
Note that I substituted actual variables in the query with question marks, which is being correct way of using prepared statements, that otherwise become useless and render all your transition from mysqli fruitless (especially given that mysqli supports prepared statements as well).
Unfortunately, PHP has two kinds of errors - exceptions and errors proper. And try-catch can catch only the former. In order to handle all kinds of errors, an error handler can be used. You can see a very basic example of one in my article on PHP error reporting.
The last note: sending an email every time an error occurs on the site is not the wisest move. Although in your case it could be justified, given PHP is only involved when a user submits a form, but on a regular site, where PHP is used to handle every page, it can lead to thousands emails. Or even in your case, spammers may target your forms and send thousands requests as well (which itself may cause some overflow error and therefore thousands emails in the inbox). Instead of sending emails manually, consider using a dedicated error monitoring software, such as Sentry. It will send only new errors, as well as aggregated error info.
new PDO raises an exception if the connection fails. Use an exception handler:
try {
$database_connection = new PDO($data_source_name, $db_username, $db_password);
} catch (PDOException $e) {
// error handling
}
What I was trying to do is inserting all query errors into a database, but, it doesnt work. I wanted to do this:
<?php
include('db_settings.php');
$query = $conn->query("mysql_query here");
if (!query) {
$error = $conn->error;
$log_error = $conn->query("INSERT INTO tab (log) VALUES ('$error')");
}
?>
However, this does not work, the error is not being submitted into the db.
Does any of you know some workaround for this?
(before someone asks, all parameters of DB and variables are correct).
You simply shouldn't do that.
Do not try to use a medium that failed you that very instant!
Let's take one of your recent questions: The very error message that troubled you here, Mysqli Commands out of sync will prevent your wunderlogging from functioning! Your database won't get back to sync by magic! And thus will effectively prevent you from logging its own error. And so you simply will never have an idea it occurred.
Let errors to be logged, and then you'll be able find them all.
add these three lines at the top of your code,
ini_set('log_errors',1);
error_reporting(E_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
and then check the server error log.
This is how everyone is doing it and there is no reason to devise such an awkward and illogical device.
I have to include database connection in some PHP scripts. So I require() first and then put my queries after. If viewed as a single script, it amounts to something like this:
Try {
$connect = new PDO("mysql:host={$DB_host};dbname={$DB_name}; charset=utf8mb4",$DB_user,$DB_pass);
$connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
Catch(PDOException $e) {
echo $e->getMessage();
}
// Then I put the queries here
It works, but my question is: is this safe? I've seen in most tutorials that they put all the queries within Try { } curly brackets. And what is the difference between putting the queries within Try { } and putting it after ?
If for whatever reason your query fails the program will crash at the line of code that was executing the query. There may be justification to do this if this a behavior that you desire in your code, for whatever reason.
Without the error handling your program will just break whenever an error is thrown. So unless you specifically need to have the query outside of the try catch (I couldn't guess what for), then you will just be creating trouble for yourself in the future.
If an exception occurs during your query (For instance, if it can't execute the query properly because of a missing quote), the error will not be caught and the execution of your script will halt.
Error handling is usually always preferred when possible. If there is a problem fetching data, inserting data, or simply a typo in your query, you should always have a way to notify a user that an error has occurred (And also, log it for further investigation for yourself.)
I have a decent understanding of PHP errors (I'm not using or die() as much as I used to) but I still don't fully understand how to deliver the fatal error messages to the user in a way that isn't plain text in the upper left hand corner.
Say I have this code:
try {
$conn = new PDO("mysql:host=host; dbname=userDB", $username, $password);
$stmt = $conn->prepare("UPDATE employee SET password = :newpass WHERE password = :tempPass");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt->execute(array('tempPass' => $tempPass, 'newpass' => $newpassword));
echo'success!<br>';
echo"<a href='main_login.php'>Back</a>";
}
catch(PDOException $e) {
echo 'ERROR: '. $e->getMessage();
}
That's all well and good, but whether it's successful or not, it's an ugly message of affirmation.
Should I be redirecting my users to a different page to deliver this message?
If so, how should I store/transport the messages to the individual page? I don't want to have to create user_success.php, user_failure.php, user_epic_failure.php, every_other_case.php. It's not efficient and it limits what I can do.
I've heard that storing these messages into a session error variable is a good way to do this but is it the best way to do this? Should there be a dedicated error page and dedicated success page that just serve as templates to which I pass my messages?
I just want pretty error messages. Is that so much to ask?
never display system error message to the user, this could give a potential hacker information about your system. have a generic error page that you redirect to from the catch clause if a fatal error occurs.
for all other errors that don't cause a fatal error and stop the code executing, just continue executing your code and display an error message on top of your page if necessary.
What is the "proper" way to deal with errors when manipulating a sql database with php?
What Im currently doing looks like this:
$connection = new mysqli('hostname', 'user', 'pass', 'database');
if ($connection->connect_errno) {
reportError("DB_CONNECTION_ERROR", $connection->connect_errno, $connection->connect_error);
displayError("DB_CONNECTION_ERROR");
}
$stmt = $connection->stmt_init();
$q = "query";
$stmt->prepare($q);
$stmt->bind_param('s', $username);
$stmt->execute();
reportError() is part of an error handling file I wrote and logs the error in a database
displayError() is part of the same file and tells the page what to display (as opposed to displaying the actual error).
However Im not sure of how to check for other errors, such as whether a statement was successfully prepared or whether a query was successful. Any recommendations appreciated!
Don't you find it quite odd to write database connection errors... into database?
I see also no point in having custom displayError() function. It should be generic _503() function, sending corresponding header along with general excuses.
I see no point in having custom logError() function either. PHP quite capable to log errors itself. trigger_error() serves me best.
Im not sure of how to check for other errors, such as whether a statement was successfully prepared
Ah, this one. Exceptions.
Mysqli should throw an exception if something went wrong. See mysqli_sql_exception for more details.
In your client code, you can then wrap your code in try/catch blocks:
try {
} catch (Exception $e) {
}
Sometimes, there are exceptions that can't be solved within a try/catch block, for example, the database server is down, and a site that is heavily reliant on the database would not be able to function anyway.
For those very critical problems, you can allow the exception to bubble upwards. You should then set an exception handler at the beginning of your script to catch those exceptions, notify the administrator and do some logging, then display an 500 error page to the user.