I have three classes each handling a different functionality when connecting to my database.
Class 1 with name ConnectDB() which handles MySqli() connection; Class 2 with name MessageOut() which gives error and success messages via a $_SESSION['message'] key to the user UI and Class 3 with name WebApp() uses the MySqli connection to retrieve the data from the database and pass it to the UI.
This differs from how I normally do database connection, collection and display. Normally I just created a lot of functions in three files and called them if I needed them. But my site is getting bigger and more complicated so I am re-organising everything.
My current problem is when I create the db connection within my 3rd class it is not throwing the error I programmed.
class ConnectDB {
//Connecting to database
public function connect() {
//connecting to mysql
#$conn = new mysqli(DB_HOST, DB_USERRW, DB_PASSWRW, DB_DBASE);
// check validity of database connection
if (mysqli_connect_errno()) {
return false;
exit();
}
// select the database to use
return $conn;
}
}
ini_set('display_errors', 1);
ini_set('log_errors',1);
error_reporting(E_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
class WebApp {
protected $db;
function __construct(){
$this->connector = new ConnectDB();
$this->messager = new MessageOut();
try {
$this->db = $this->connector->connect();
if(!$this->db) {
throw new Exception(' Database connection is currently unavailable.');
}
} catch (Exception $e) {
mysqli_report(MYSQLI_REPORT_OFF);
$errors = array();
$message = $e->getMessage();
//populate array with message and divert output to error class functions
array_push($errors, $message);
$this->messager->erroutput($errors);
}
}
public function selectIdata() {
//select data
try {
$query = "SELECT *
FROM thetable";
$stmt = $this->db->prepare($query);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($idata);
$result = [];
if ($stmt->num_rows > 0) {
while ($stmt->fetch()) {
$result[] = $idata;
}
return $result;
} else {
return false;
}
} catch (Exception $e) {
mysqli_report(MYSQLI_REPORT_OFF);
$errors = array();
$message = $e->getMessage();
//populate array with message and divert output to error class functions
array_push($errors, $message);
$this->messager->erroutput($errors);
}
}
}
I changed my localhost defined password to the wrong one and loaded the file, but the error is given on line 10 within my 1st class even though the error is suppressed. The idea is to check the connection before using it.
How do I get the error thrown with in my 3rd class giving me the 'Database connection is currently unavailable.' message?
Edit
So I re-assessed what I did and set a try-catch block within the constructor of my 3rd class, but know I get an error: Fatal error: Uncaught Error: Call to a member function prepare() on null in the 3rd class on line 41 where the db connection is used.
It looks like you misunderstand the idea of error reporting. I will try to clear up the general concept to you.
Error reporting is to inform the developer of bad coding, bugs and other potential issues which need to be fixed. Error reporting is not for the users of the product. When you are developing you can enable display_errors for yourself, but you should never leave it in the code. You should however always log the errors. PHP has a very good error logger, but I can understand how it might not be enough for you and you would like to log more information together with the error message.
You can write a generic error handler and catch all errors and exceptions thrown by your application and log it to a secure location on the server using your own logger software. Don't catch the exceptions in the middle of your code. Such logger needs to be central to your application and in a separate file to be able to catch all errors.
For this reason, your try-catch is not very useful because you have it in more than one place and it is intertwined with your application code. Also, you are only catching exceptions and disregard errors. You should catch both. Use something like catch(\Throwable $e) to catch both.
# is an error suppression operator. It should be avoided at all cost. If you can't avoid it then you need to rewrite the code to avoid it. What you are doing there with the mysqli connection you are actually ignoring the error twice. First, you use # and then you kill your script. Do not kill the script. Do not silence errors. Let your errors bubble up and let them be caught by your error handler.
If you think about it, your class ConnectDB is pretty useless. To connect to the database you always need the same 3 lines. mysqli is already a class so wrapping 3 lines in another class is pointless. The correct code should be nothing more than:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$conn = new mysqli(DB_HOST, DB_USERRW, DB_PASSWRW, DB_DBASE);
$conn->set_charset('utf8mb4');
Your current script exits when mysqli can't connect, but even if you don't silence the errors and you have error reporting switched on, then there is no chance the variable will be empty. if(!$this->db) becomes another fallacy. Besides, why would you want to throw an exception when you just have silenced one? This bring me to another point. Why throw an exception when you immediately catch it? Surely, the whole logic there is nothing more than a simple if statement:
if(!$this->db) {
$this->messager->erroutput([' Database connection is currently unavailable.']);
}
I see you have named your class MessageOut and I really hope you do not expose the error messages to the user. This is not only terrible user experience, but also a security risk. You should instead implement a nice HTTP 500 error page or if your application is sophisticated enough, your own error page which will be displayed when your error handler catches an error.
Switching off mysqli error reporting once you caught some error serves no purpose. Just remove mysqli_report(MYSQLI_REPORT_OFF); from your code.
To visualise what I am describing consider this code:
<?php
// ini_set('display_errors', 1);
ini_set('log_errors', 1);
error_reporting(E_ALL);
class WebApp {
protected $db;
function __construct() {
$this->messager = new MessageOut();
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // it can also be at the top of the script, but it makes more sense to put it together with the rest of mysqli code
$this->db = new \mysqli(DB_HOST, DB_USERRW, DB_PASSWRW, DB_DBASE);
$this->db->set_charset('utf8mb4');
}
public function selectIdata() {
//select data
$query = "SELECT *
FROM thetable";
$stmt = $this->db->prepare($query);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($idata);
$result = [];
while ($stmt->fetch()) {
$result[] = $idata;
}
return $result;
}
}
try {
$app = new \WebApp();
} catch (\Throwable $e) {
// log your errors here.
}
It's not the perfect code because the error handler is not there, and it should also be in a separate file, but the general idea is to avoid unnecessary code in your application logic. Do not try-catch. Do not silence the errors and do not add code which serves no purpose. Keep it simple.
Related
This question already has an answer here:
What to do with mysqli problems? Errors like mysqli_fetch_array(): Argument #1 must be of type mysqli_result and such
(1 answer)
Closed 5 years ago.
I'm trying to get my head around MySQli and I'm confused by the error reporting.
I am using the return value of the MySQLi 'prepare' statement to detect errors when executing SQL, like this:
$stmt_test = $mysqliDatabaseConnection->stmt_init();
if($stmt_test->prepare("INSERT INTO testtable VALUES (23,44,56)"))
{
$stmt_test->execute();
$stmt_test->close();
}
else echo("Statement failed: ". $stmt_test->error . "<br>");
But, is the return value of the prepare statement only detecting if there is an error in the preperation of the SQL statement and not detecting execution errors? If so should I therefore change my execute line to flag errors as well like this:
if($stmt_test->execute()) $errorflag=true;
And then just to be safe should I also do the following after the statement has executed:
if($stmt_test->errno) {$errorflag=true;}
...Or was I OK to start with and the return value on the MySQLi prepare' statement captures all errors associated with the complete execution of the query it defines?
Thanks
C
Each method of mysqli can fail. Luckily, nowadays mysqli can report every problem to you, all you need is ask. Simply add this single line to the connection code,
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
And after that every error will reveal itself. No need to test any return values ever, just write your statements right away:
$stmt = $mysqli->prepare("INSERT INTO testtable VALUES (?,?,?)");
$stmt->bind_param('iii', $x, $y, $z);
$stmt->execute();
When the error occurs at any step, it will throw a usual PHP Exception that can be handled or just reported the same way as any other PHP error. Just make sure you configured PHP error reporting properly, i.e. on the dev server errors are displayed on-screen and on the production server errors are never displayed but logged instead.
Completeness
You need to check both $mysqli and $statement. If they are false, you need to output $mysqli->error or $statement->error respectively.
Efficiency
For simple scripts that may terminate, I use simple one-liners that trigger a PHP error with the message. For a more complex application, an error warning system should be activated instead, for example by throwing an exception.
Usage example 1: Simple script
# This is in a simple command line script
$mysqli = new mysqli('localhost', 'buzUser', 'buzPassword');
$q = "UPDATE foo SET bar=1";
($statement = $mysqli->prepare($q)) or trigger_error($mysqli->error, E_USER_ERROR);
$statement->execute() or trigger_error($statement->error, E_USER_ERROR);
Usage example 2: Application
# This is part of an application
class FuzDatabaseException extends Exception {
}
class Foo {
public $mysqli;
public function __construct(mysqli $mysqli) {
$this->mysqli = $mysqli;
}
public function updateBar() {
$q = "UPDATE foo SET bar=1";
$statement = $this->mysqli->prepare($q);
if (!$statement) {
throw new FuzDatabaseException($mysqli->error);
}
if (!$statement->execute()) {
throw new FuzDatabaseException($statement->error);
}
}
}
$foo = new Foo(new mysqli('localhost','buzUser','buzPassword'));
try {
$foo->updateBar();
} catch (FuzDatabaseException $e)
$msg = $e->getMessage();
// Now send warning emails, write log
}
Not sure if this answers your question or not. Sorry if not
To get the error reported from the mysql database about your query you need to use your connection object as the focus.
so:
echo $mysqliDatabaseConnection->error
would echo the error being sent from mysql about your query.
Hope that helps
This question already has an answer here:
What to do with mysqli problems? Errors like mysqli_fetch_array(): Argument #1 must be of type mysqli_result and such
(1 answer)
Closed 5 years ago.
I'm trying to get my head around MySQli and I'm confused by the error reporting.
I am using the return value of the MySQLi 'prepare' statement to detect errors when executing SQL, like this:
$stmt_test = $mysqliDatabaseConnection->stmt_init();
if($stmt_test->prepare("INSERT INTO testtable VALUES (23,44,56)"))
{
$stmt_test->execute();
$stmt_test->close();
}
else echo("Statement failed: ". $stmt_test->error . "<br>");
But, is the return value of the prepare statement only detecting if there is an error in the preperation of the SQL statement and not detecting execution errors? If so should I therefore change my execute line to flag errors as well like this:
if($stmt_test->execute()) $errorflag=true;
And then just to be safe should I also do the following after the statement has executed:
if($stmt_test->errno) {$errorflag=true;}
...Or was I OK to start with and the return value on the MySQLi prepare' statement captures all errors associated with the complete execution of the query it defines?
Thanks
C
Each method of mysqli can fail. Luckily, nowadays mysqli can report every problem to you, all you need is ask. Simply add this single line to the connection code,
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
And after that every error will reveal itself. No need to test any return values ever, just write your statements right away:
$stmt = $mysqli->prepare("INSERT INTO testtable VALUES (?,?,?)");
$stmt->bind_param('iii', $x, $y, $z);
$stmt->execute();
When the error occurs at any step, it will throw a usual PHP Exception that can be handled or just reported the same way as any other PHP error. Just make sure you configured PHP error reporting properly, i.e. on the dev server errors are displayed on-screen and on the production server errors are never displayed but logged instead.
Completeness
You need to check both $mysqli and $statement. If they are false, you need to output $mysqli->error or $statement->error respectively.
Efficiency
For simple scripts that may terminate, I use simple one-liners that trigger a PHP error with the message. For a more complex application, an error warning system should be activated instead, for example by throwing an exception.
Usage example 1: Simple script
# This is in a simple command line script
$mysqli = new mysqli('localhost', 'buzUser', 'buzPassword');
$q = "UPDATE foo SET bar=1";
($statement = $mysqli->prepare($q)) or trigger_error($mysqli->error, E_USER_ERROR);
$statement->execute() or trigger_error($statement->error, E_USER_ERROR);
Usage example 2: Application
# This is part of an application
class FuzDatabaseException extends Exception {
}
class Foo {
public $mysqli;
public function __construct(mysqli $mysqli) {
$this->mysqli = $mysqli;
}
public function updateBar() {
$q = "UPDATE foo SET bar=1";
$statement = $this->mysqli->prepare($q);
if (!$statement) {
throw new FuzDatabaseException($mysqli->error);
}
if (!$statement->execute()) {
throw new FuzDatabaseException($statement->error);
}
}
}
$foo = new Foo(new mysqli('localhost','buzUser','buzPassword'));
try {
$foo->updateBar();
} catch (FuzDatabaseException $e)
$msg = $e->getMessage();
// Now send warning emails, write log
}
Not sure if this answers your question or not. Sorry if not
To get the error reported from the mysql database about your query you need to use your connection object as the focus.
so:
echo $mysqliDatabaseConnection->error
would echo the error being sent from mysql about your query.
Hope that helps
I was just wondering something. In index.php, I am currently doing something like this
function performFtpOperation() {
global $config;
try {
$ftp = new FTP\FtpClient();
$ftp->connect($config::FTP_SERVER);
$ftp->login($config::FTP_USER, $config::FTP_PASSWORD);
} catch (Exception $e) {
echo 'Error: ', $e->getMessage();
}
}
What I was wondering is if that try catch block is needed? Reason I question it is because my FTP class throws errors if something goes wrong. For instance this is the connect function
public function connect($host, $ssl = false, $port = 21, $timeout = 90)
{
if ($ssl) {
$this->conn = #$this->ftp->ssl_connect($host, $port, $timeout);
} else {
$this->conn = #$this->ftp->connect($host, $port, $timeout);
}
if (!$this->conn) {
throw new Exception('Unable to connect');
}
return $this;
}
So would a try/catch be needed if errors are handled within the class?
Thanks
When an exception is thrown, the code following it will not be executed, and PHP will try to find the matching "catch" block.
If an exception is not caught, a fatal error will be issued with an "Uncaught Exception" message.
Proper exception code should include:
Try - A function using an exception should be in a "try" block. If the exception does not trigger, the code will continue as normal. However if the exception triggers, an exception is "thrown"
Throw - This is how you trigger an exception. Each "throw" must have at least one "catch"
Catch - A "catch" block retrieves an exception and creates an object containing the exception information
The connect class throws exceptions that you need to catch and handle somewhere in your code. It is up to you where to handle it depending on your application design and requirements.
If you decided that you wanted to handle them in the performFtpOperation function, then your use of try...catch there is correct.
If you don't handle them in the performFtpOperation function, then they will bubble up to the code that calls the performFtpOperation function and you can catch & handle them there if you like using try...catch similar to how you did it here. Just remember that you need to catch them somewhere.
Well since you are throwing an exception from within your FTP code, then Yes.
You need to try catch if you work with exeptions, otherwise you generate fatal errors and your script/site will stop work at this point, so try/catch is a part of the exeption handling.
you can inform yourself about this in the php manual
http://php.net/manual/en/language.exceptions.php
try {
self::$dbinstance = new PDO(
"mysql:host=$c[host];dbname=$c[dbname]", $c['user'], $c['password']
);
self::$dbinstance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
echo "Errors" . $e->getMessage();
}
In the above code, if PDO fails to connect to the host, a fatal error reveals the username and password.
Uncaught exception 'PDOException' with message 'SQLSTATE[HY000] [2003]
Can't connect to MySQL server on '172.25.102.65' (10060)' in
D:\xampp\htdocs\mytest\wh_client_2.1\classes\importmodule-class.php:33 Stack trace: #0
D:\xampp\htdocs\mytest\wh_client_2.1\classes\importmodule-class.php(33): PDO-
>__construct('mysql:host=172....', 'host', 'password') #1
One possible way is to turn the display_error=0 off in php.ini, but this way I won't able to know that when my host is not responding.
Is there a way I can modify the error message?
There is a difference between error handling and error reporting.
Error handling is the process of preventing your end users to see any stack trace, vital information or automatically generated error messages. It can also modify the way your script runs by using a try catch block.
Error reporting defines which information will be reported by a given script.
To handle errors properly, I think that ini_set('display_errors',0); is the better approach. You do not want any error message displaying on the screen.
However, I want to have all possible information on errors, so I use error_reporting(E_ALL);.
Errors are written in a file, error_log, which usually resides at the same level as your index.php (or any PHP file called directly). You can also access it from your cPanel.
Your error is probably uncaught because your code is in a namespace, whereas you want to catch the global namespace PDOException. Use a \ to indicate your script you're looking for the global PDOException. Once you catch your error, you can echo the content you want, using the normal methods of the PDOException class.
try {
$db = new PDO (/*connection infos*/);
}
catch (\PDOException $e) {
switch ($e->errorCode()) {
case 'HY000':
// Or whatever error you are looking for
// here it's the general error code
mail('your#email.com','connection problem',$e->getTraceAsString());
$db = new PDO (/*rollback connection infos of a local database*/);
break;
}
}
That would send you a mail, containing the trace of the error, preventing your user from seeing it while telling you something is wrong.
Here is the reference for the error codes returned by PDO statements.
When your host is not responding you will know all right - your host will stop responding. Then you have to peek into the error log and find the error message with the particular error.
So, just keep with display_errors=0 as it's a must-have in a production environment anyway.
No, don't try to throw the exception as it will spit out such critical information... Handle them with some appropriate custom error messages and handle those exceptions inside your custom logging functions...
You must be doing something similar to this...
<?php
try {
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'uname', 'pass');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->query('bla bla bla bla'); //<---- This will definitely fail!!!!
} catch(PDOException $ex) {
echo "An error occurred!";
file_put_contents('somefile.txt', $ex->getMessage(), FILE_APPEND);
}
As you can see, the above query is indeed going to fail. So the end user will be seeing just An error occurred! message, but the error will be logged to your somefile.txt file.
You can do something like this:
<?php
// connect
try
{
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
}
catch (PDOException $e)
{
$dbh = NULL;
}
// check if connected
if($dbh)
{
// run queries
}
else
{
die('Oops! Our server has encountered an error, please try again later');
}
?>
There is this question that I found:
What is the advantage of using try {} catch {} versus if {} else {}
If you can add anything to it then please do as I am new to PDO, also what does this mean;
$dbc->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
On the MySQL website it says "ensures the creation of exceptions rather than errors" but I do not understand this, can anyone please elaborate on it please.
Exceptions are catchable via try/catch and are classes with properties, while proceedural errors cannot and are not. Proceedural errors are instead handled by PHP's native error handling.
You can observe the behavior difference by manually triggering them.
to throw an Exception:
throw new Exception();
to trigger a proceedural error:
trigger_error($message, U_USER_ERROR);
Exceptions are bassically the OO way of error handling. Find more information about Exceptions here: http://php.net/manual/en/language.exceptions.php
Well, they are pretty similar. In PHP native error handling you can trow your own errors with trigger_error($error, [$level]) as you can throw your own exceptions with throw new MyException($error); you can set a default error handler with set_error_handler() which will manage all PHP error (except parsing) your own way as you can set a default exception handler with set_exception_handler(). Both PHP native errors and exceptions are automatically triggered/thrown somehow: PHP native errors compiling the script, exceptions if you are using specific items such as (PDO) or something.
Now let's try the same code with different approaches:
With PHP native errors you can do things such as:
$db = new Database();
if ($db === NULL) { trigger_error("Cannot connect to the database", E_USER_ERROR); }
$result = $db->query("UPDATE charlieiscool SET iloveunicorns = 1 WHERE userid = 1");
if (!$result) { trigger_error("Error updating table", E_USER_ERROR); }
$file = 'log.php';
if (!file_exists($file) or !file_get_contents($file)) { trigger_error("$file not found", E_USER_ERROR); }
require($file);
I think this does not really need any explanation. If an error is triggered, the entire script is skipped and you see the error. There are no more things you can do; you could set E_USER_ERROR or E_USER_NOTICE or E_USER_WARNING and handle them differently, but you have not that big choice. Now take a look at a possible OOP approach with try{} catch() {} blocks:
try {
$db = new Database();
if (!$db) { throw new DBEx("Cannot connect to the database"); }
$result = $db->query("UPDATE charlieiscool SET iloveunicorns = 1 WHERE userid = 1");
if (!$result) { throw new QueryEx("Query failed"); }
} catch (PDOException $e) {
echo $e->getMessage();
} catch (DBEx $e) {
$e->customFunction();
} catch (QueryEx) {
$e->customFunctionForQuery();
}
try {
$file = 'log.php';
if (!file_exists($file) or !file_get_contents($file)) { throw new FileEx("$file does not exists"); }
require($file);
} catch (FileEx) {
$e->fileGenerate();
$e->logError();
}
The main difference is that if the first try{} block throw an exception the second try{} will be executed any way. In fact if an exception is thrown, only the rest of the script inside that try{} block will be skipped.
Another difference (the one i love most) is that you can create several classes (extending mains Exception or PDOException or others) and customize very much your error handling behavior. You have unlimited possibilities to customize your classes, adding functions, editing already existing ones. You can add specific function (such as $e->fileGenerate();) and call them inside the catch() {} block where needed.
Notice also that if you want your entire script to stop if an error occurred, means that that error needs trigger_error(); instead if you want that an error only stops a specific block of code related to that error then it's time for try and catch.
You should not use one replacing the other, you should use one beside the other evaluating each errors as it is.
By the way PDO::setAttribute() change default values and options in your database handler. You can for example change your default fetch (used in PDOStatement::fetch()) with $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);.
References:
PDO::setAttribute()
set_error_handler()
set_exception_handler()
Exceptions
Predefined exceptions