I have Sentry keeping track of uncaught exceptions in my PHP application, and I noticed a peculiar uncaught exception from PDO. The code looks like this:
/**
* #return boolean TRUE if the connection to the database worked; FALSE otherwise.
*/
public function verifyDatabase() {
try{
$this->pdo->query('SELECT 1');
return true;
}
catch (\PDOException $e) {
echo 'Lost connection to database: ' . $e->getMessage() . PHP_EOL;
return false;
}
}
This should catch errors like "MySQL server has gone away", and it indeed works on my development machine. However, Sentry recently recorded this error:
ErrorException
PDO::query(): MySQL server has gone away
According to sentry, this was thrown by the $this->pdo->query('SELECT 1'); statement above. Errors like this should have been caught by the try/catch. Why is PDO throwing an ErrorException rather than a PDOException?
I can't reproduce an ErrorException.
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', ..., ...);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
sleep(20); // during this sleep, I stop my MySQL Server instance.
$result = $pdo->query("SELECT 1");
Output:
Warning: PDO::query(): MySQL server has gone away
Warning: PDO::query(): Error reading result set's header
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Stack trace:
#0 /Users/bkarwin/Documents/SO/pdo.php(8): PDO->query('SELECT 1')
This shows it throws a PDOException when the server has gone away, not an ErrorException.
Tested with MySQL 5.6.37 and PHP 7.1.23.
I wonder if the code you show in your question is actually the code that is deployed and throwing the exception to Sentry. Perhaps you have some code like:
catch (\PDOException $e) {
throw ErrorException($e->getMessage());
}
Either in your verifyDatabase() function, or else in the code that calls verifyDatabase(). In other words, what does your app do when verifyDatabase() returns false?
Okay, I think I've figured it out. It appears that this is related to a bug in which the PDO MySQL driver emits warnings even when they are supposed to be disabled (See also: this answer). I believe Sentry is then catching these as errors.
I was finally able to replicate, and solve this, by modifying Bill Karwin's test script:
// Initialize the Sentry reporting client
$ravenClient = new \Raven_Client(SENTRY_KEY);
$ravenClient->install();
echo 'Connecting...' . PHP_EOL;
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo 'Connected. Waiting...' . PHP_EOL;
sleep(20); // during this sleep, I stop my MySQL Server instance.
echo 'Querying...' . PHP_EOL;
try {
$result = $pdo->query("SELECT 1");
}
catch(\PDOException $e) {
echo 'Caught PDOException ' . $e->getMessage() . PHP_EOL;
}
This will print the following:
Connecting...
Connected. Waiting...
Querying...
PHP Warning: PDO::query(): MySQL server has gone away in /home/xxx/src/test.php on line 37
PHP Stack trace:
PHP 1. {main}() /home/xxx/src/test.php:0
PHP 2. PDO->query() /home/xxx/src/test.php:37
Caught PDOException SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Done.
And Sentry will record an ErrorException on the query() line.
I was able to solve this issue by implementing the "solution" posted by jferrer on the PHP bug report.
// Convert NOTICE, WARNING, ... in Exceptions
$convertErrorToException = function ($level, $message, $file, $line){
throw new ErrorException($message, 0, $level, $file, $line);
};
// The $previousErrorHandler will contain Sentry's handler
$previousErrorHandler = set_error_handler($convertErrorToException);
try {
$result = $pdo->query("SELECT 1");
}
catch(\PDOException $e) {
echo 'Caught PDOException ' . $e->getMessage() . PHP_EOL;
}
catch(\ErrorException $e) {
echo 'Caught ErrorException ' . $e->getMessage() . PHP_EOL;
}
// Restore Sentry as the default handler
set_error_handler($previousErrorHandler);
This results in just the ErrorException being thrown and caught:
Connecting...
Connected. Waiting...
Querying...
Caught ErrorException PDO::query(): MySQL server has gone away
Done.
Related
My question:
Why does new PDO() not display an error message if I give it the name of a database that does not exist on the server?
My database server is CockroachDB v21.1.8 which supports most of PostgreSQL's syntax.
I was working on improving the error handling of my website when I received this error message:
SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "recipes" does not exist
The above error message came from this query:
$this->link->query("SELECT recipeid,name FROM recipes ORDER BY name")
Even though the error message is correct, it is not the message I expected. I was expecting a message that said database not found or something similar because I told pgsql to connect to a database that does not exist.
My database connection code:
try {
$this->link = new PDO('pgsql:host='.$this->serverName.';port=26257;dbname='.$usedb.';sslmode=require;sslrootcert='.$rootcert.';sslkey='.$userkey.';sslcert='.$usercert, $this->userName, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} catch (PDOException $e) {
echo 'Caught PDOExcetion<br>';
echo 'Error Message: '.$e->getMessage();
echo '<br/>Error Code: '.$e->getCode();
exit(1);
}
I am playing with try - catch block:
<?php
try {
$str = "http://rejstrik-firem.kurzy.cz/73631604";
$domOb = new DOMDocument();
$html = $domOb->loadHTMLFile($str);
$domOb->preserveWhiteSpace = false;
$container = $domOb->getElementById('ormaininfotab');
echo $container; // <========= this is intended error which I want catch
}
catch (Exception $e) {
echo "Exception" . $e->getMessage() . ". File: " . $e->getFile() . ", line: " . $e->getLine();
}
catch (Error $e) {
echo "Error" . $e->getMessage() . ". File: " . $e->getFile() . ", line: " . $e->getLine();
}
?>
My result is this:
Catchable fatal error: Object of class DOMElement could not be
converted to string in /var/www/html/cirkve_ares/test.php on line 8
Why is not this error catched by second catch?
As user2782001 mentioned this is not a bug in the eyes of PHP dev's. They even noted that these type of errors should be referenced as 'recoverable':
we should get rid of any references to "catchable" fatal errors (if they still exist) in favor of "recoverable" fatal errors. Using "catchable" here is confusing as they cannot be caught using catch blocks.
On the ErrorException manual page there is a neat workaround converting those "catchable/recoverable" errors to ErrorException.
<?php
function exception_error_handler($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
?>
now you will be able to catch those errors with:
<?php
try {
// Error code
} catch (Error $e) { // this will catch only Errors
echo $e->getMessage();
}
?>
or
try {
// Error code
} catch (Throwable $t) { // this will catch both Errors and Exceptions
echo $t->getMessage();
}
?>
Someone reported this as a bug to PHP's devs, who promptly decided it was not a bug. https://bugs.php.net/bug.php?id=72948&edit=3
This case has been intentionally omitted ...(in practice you can simply convert the recoverable fatal to an exception using an error handler...)
So you still have to use the
set_error_handler()
function, which we were all hoping to leave behind. PHP's devs are so good at never letting your day be too sunny...
There might be some fatal errors which are not even caught by set_error_handler() or \Throwable.
The below implementation will catch the errors which are not even caught by \Throwable as tested in php 7.1. It should only be implemented in your development environment(by just adding it in your development config file) and shouldn't be done in production.
Implementation
register_shutdown_function(function () {
$err = error_get_last();
if (! is_null($err)) {
print 'Error#'.$err['message'].'<br>';
print 'Line#'.$err['line'].'<br>';
print 'File#'.$err['file'].'<br>';
}
});
Example Error
Error# Class Path/To/MyService contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Path/To/MyServiceInterface::add)
Line# 12
File# Path/To/MyService.php
It has been "fixed" as of PHP 7.4 according with Manual PHP, now it throws exception:
Existing recoverable fatal errors in string conversions have been converted to Error exceptions.
I want to check if accessing mysql is possible or not.
I don't wont to have an error message, only true/false if the connection is ready or not.
I tried to do it with this :
$appDB = new mysqli($data[0]['MYSQL']['HOST'], $data[0]['MYSQL']['BENUTZER'], $data[0]['MYSQL']['PW'], $data[0]['MYSQL']['TABELLE']);
if ($appDB->connect_error)
{ $res['code']=FALSE; }
else
{
$appDB -> close();
$res['code']=TRUE;
}
But always a error message will come up - how can i prevent this message from showing ?
"<b>Fatal error</b>: Uncaught exception 'mysqli_sql_exception' with message 'Access denied for user ''
#'localhost' (using password: NO)' in /is/htdocs/...mypath
.php:33
Stack trace:
#0 /is/htdocs...mypath.myfile.php(33): mysqli->mysqli('', ''
, '', '')
#1 {main}
thrown in <b>/is/htdocs/wp1076647_373QG1K1B0/butobo/module/4/code/cms_checkcon.php</b> on line <b>33
</b><br />"
You need to "catch" this fatal exception via a try/catch statement. This allows you to handle the fatal error and then instead of your script crashing due to it, you can output an error message and exit instead :D
PHP Exceptions: http://php.net/manual/en/language.exceptions.php
I hope this helps.
try
{
$appDB = new mysqli($data[0]['MYSQL']['HOST'], $data[0]['MYSQL']['BENUTZER'], $data[0]['MYSQL']['PW'], $data[0]['MYSQL']['TABELLE']);
if ($appDB->connect_error)
{ $res['code']=FALSE; }
else
{
$appDB -> close();
$res['code']=TRUE;
}
}
catch (Exception $e)
{
echo 'Caught exception: ', $e->getMessage(), "\n";
exit;
}
This will change the ERROR to a WARNING, which will not break your script. From there, if you want to hide everything EXCEPT fatal errors, you can use this line of PHP:
error_reporting(E_ERROR);
Error message would display because your PHP is configured to do so. To make it not display, simply configure it like
ini_set('display_errors', "0");
However, this code also stops the execution. It order to prevent it, you have to catch the exception. So it should be like
try {
$appDB = new mysqli($data[0]['MYSQL']['HOST'], $data[0]['MYSQL']['BENUTZER'], $data[0]['MYSQL']['PW'], $data[0]['MYSQL']['TABELLE']);
$res['code']=TRUE;
}
catch (Exception $e)
{
error_log($e);
$res['code']=FALSE;
}
while setting error_reporting(E_ERROR); is rather stupid as it will prevent you from seeing all other errors
I am using zend framework 1.12 for my project. I want to catch all types of fatal errors and send them to an email address for quick fix. I have written the below mentioned code in Bootstrap.php file for this purpose.
protected function _initFatalErrorCatcher()
{
register_shutdown_function(array($this, 'errorlogHandler'));
}
public function errorlogHandler()
{
$e = error_get_last();
if (!is_null($e)) { //fatal error
$msg = 'Fatal error: ' . $e['message'];
$msg .= ' in' . $e['file'];
$msg .= ' on line: ' . $e['line'];
$mail = new Zend_Mail('utf-8');
$mail->setBodyHtml($msg);
$mail->setFrom('zzz#z.com');
$mail->addTo('yyy#y.com');
$mail->setSubject('check this error');
$mail->send();
}
}
Using the above code, i am able to send fatal errors other than database connection related errors and query related errors to email. I followed the instructions from Catch Zend PDO Exception as well, but i believe i am missing something as its not working.
Any help on this will be appreciated.
EDIT:
I am also using Zend_Log to write the error logs in a log-file. But, using this i could not find a way to write the fatal errors. Code for this is given below.
$writer = new Zend_Log_Writer_Stream(APPLICATION_PATH . "/../data/log-file.log");
$errors = $this->_getParam('error_handler');
$exception = $errors->exception;
$log = new Zend_Log($writer);
$log->debug($exception->getMessage() . "\n" . $exception->getTraceAsString());
Scenario for database connection related issue:
If there is any error in host name, database name or in user name, it shows a Fatal error in browser like below. But its not detected by register_shutdown_function() or Zend_Log().
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000] [1044] Access denied for user 'AAAA'#'%' to database 'BBBB'' in /var/www/project_name/library/Zend/Db/Adapter/Pdo/Abstract.php on line 144 PDOException: SQLSTATE[42000] [1044] Access denied for user 'AAAA'#'%' to database 'BBBB' in /var/www/project_name/library/Zend/Db/Adapter/Pdo/Abstract.php on line 129
The post here shows an example. Basically use set_error_handler to tickle php into throwing exceptions when an error is encountered. This example from link:
<?php
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
set_error_handler("exception_error_handler");
/* Trigger exception */
strpos();
?>
Hope this helps
//$array contains the values for insert
try {
$this->db->insert('Users', $array );
} catch (Exception $e){
echo $e->getMessage();
}
I have solved it by writing the below mentioned code in Bootstrap.php file.
protected function _initDbConfig()
{
$config = new Zend_Config($this->getOptions());
$params = $config->database->toArray();
try {
$db = Zend_Db::factory('Pdo_Mysql', $params);
$db->getConnection();
} catch (Zend_Db_Adapter_Exception $e) {
// perhaps the RDBMS is not running
// code to send email goes here
} catch (Zend_Exception $e) {
// perhaps factory() failed to load the specified Adapter class
// code to send email goes here
}
}
In application.ini, i have the following code.
database.host = "localhost"
database.username = "AAAA"
database.password = "*****"
database.dbname = "BBBBB"
I am writing a script to install the database of an application in php. It working fine but when im trying to install a database that doesnt exist i want only my own error message but i keep getting the default Warning : Warning: mysqli::mysqli() [mysqli.mysqli]: (HY000/2005): Unknown MySQL server host 'kasdasd'.
So I know that the host is wrong and I want it to be so, with only my own errormessage. How do I get rid of this message?
My connectclass with parameter DBConfig $config:
$this->mysqli = new mysqli($config->m_host,
$config->m_user,
$config->m_passw,
$config->m_db);
if ($this->mysqli->connect_error) {
return false;
}
$this->mysqli->set_charset("utf8");
return true;
an easy solution would be to see if you can open the hostname using fsockopen and suppressing the errors:
$port = 80;
if($fp = #fsockopen($config->m_host,$port)){
$db = new mysqli($config->m_host,$config->m_user,$config->m_passw,$config->m_db);
}else{
echo 'hostname not recognized';
}
#fclose($fp);
Edited:
You can use
if ($mysqli->connect_error) {
/** handle your error here **/
// Throw a custom exception if you like! (see below)
// or just echo "There was an error";
}
You can further use mysqli_connect_errno() to find out wht happened and handle it accordingly.
Edit: mysqli doesn't throw erros so below is incorrect.
Wrap your code in a try-catch, then you can throw whatever kind of error you like:
try {
/** your code here **/
} catch (Exception $e) {
/** your handling here **/
// i.e.
throw new BadHostNameException($config->m_host);
// or
echo "Could not connect!":
}
Note: you should replace Exception $e with the specific kind of exception a bad host throws so catch(MysqlBadHostnameException $e) (the type of error will be in your error log from your previous attempts or you can do get_class($e) in my example above.
// Isusing a custom exception: Add this outside of your class..
class BadHostNameException extends Exception {}