php - How to catch an unexpected error? - php

I'm writing a script, where a lot of things could go wrong. I'm making if/else statements for the obvious things, that could heppen, but is there a way to catch something, that could possible heppen, but I don't know what it is yet?
For example something causes an error of some kind, in the middle of the script. I want to inform the user, that something has gone wrong, but without dozens of php warning scripts.
I would need something like
-- start listening && stop error reporting --
the script
-- end listening --
if(something went wrong)
$alert = 'Oops, something went wrong.';
else
$confirm = 'Everything is fine.'
Thanks.

Why not try...catch?
$has_errors = false;
try {
// code here
} catch (exception $e) {
// handle exception, or save it for later
$has_errors = true;
}
if ($has_errors!==false)
print 'This did not work';
Edit:
Here is a sample for set_error_handler, which will take care of any error that happens outside the context of a try...catch block. This will also handle notices, if PHP is configured to show notices.
based on code from: http://php.net/manual/en/function.set-error-handler.php
set_error_handler('genericErrorHandler');
function genericErrorHandler($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
$v = 10 / 0 ;
die('here');

Read up on Exceptions:
try {
// a bunch of stuff
// more stuff
// some more stuff
} catch (Exception $e) {
// something went wrong
}

throw new Exception('Division by zero.');
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
http://php.net/manual/en/language.exceptions.php

You should definitely use the try-catch syntax to catch any exception thrown by your script.
Additionally you can extend exceptions and implement new ones that fulfill your needs.This way, you can throw your own exceptions when you find any other kind of unexpected error (error for your script's logic).
A very short example explaining the use of extending exceptions :
//your own exception class
class limitExceededException extends Exception { ... }
try{
// your script here
if($limit > 10)
throw new limitExceededException();
}catch(limitExceededException $e){//catching only your limit exceeded exception
echo "limit exceeded! cause : ".$e->getMessage();
}catch(Exception $e){//catching all other exceptions
echo "unidentified exception : ".$e->getMessage();
}

Besides using try/catch, I think it's important to consider if you should catch an unexpected error. If it's unexpected then your code has no idea how to handle it and allowing the application to continue may produce bad data or other incorrect results. It may be better to just let it crash to an error page. I just recently had a problem where someone had added generic exception handlers to everything, and it hid the original location of the exception making the bug difficult to find.

Related

Catching fwrite() / socket errors

I am creating a library which is making a socket connection:
public function __construct(Options $options)
{
$this->responseBuffer = new Response();
$this->connection = stream_socket_client($options->fullSocketAddress());
if (!$this->isAlive($this->connection)) {
throw new DeadSocket();
}
stream_set_timeout($this->connection, $this->timeout);
$this->options = $options;
}
Sending the data to the server goes through send() method which looks like this:
public function send(string $xml)
{
try {
fwrite($this->connection, $xml);
} catch (Exception $e) {
$this->options->getLogger()->error(__METHOD__ . '::' . __LINE__ . " fwrite() failed " . $e->getMessage());
return;
}
}
The problem being here that catch doesn't capture PHP notice errors which seem to be the indicator in my case that connection itself is broken.
In case the server error happened, I am getting a <stream:error> (XMPP standard), however if socket broke or some timeout happened like this one, I can't catch it:
[22-May-2019 12:35:07 UTC] PHP Notice: fwrite(): send of 94 bytes failed with errno=110 Connection timed out in .../Socket.php on line 52
At this time I would like to know if error happened so that I can trigger reconnection, however doing any of these didn't seem helpful:
if (!is_resource($this->connection)) $this->reconnect();
if (!$this->connection) $this->reconnect();
As well as checking any of the socket_get_status() properties since socket_get_status($this->connection)['timed_out'] can be true even with alive connection.
Is there a way to catch this notice?
Also is there a way to simulate the behavior so I can reproduce it even when the connection doesn't time out?
You can set just the level by calling error_reporting, like:
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
Or, you can set your own error handler, using set_error_handler. You can find an example on the page as well:
<?php
// error handler function
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting, so let it fall
// through to the standard PHP error handler
return false;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
// function to test the error handling
function scale_by_log($vect, $scale)
{
if (!is_numeric($scale) || $scale <= 0) {
trigger_error("log(x) for x <= 0 is undefined, you used: scale = $scale", E_USER_ERROR);
}
if (!is_array($vect)) {
trigger_error("Incorrect input vector, array of values expected", E_USER_WARNING);
return null;
}
$temp = array();
foreach($vect as $pos => $value) {
if (!is_numeric($value)) {
trigger_error("Value at position $pos is not a number, using 0 (zero)", E_USER_NOTICE);
$value = 0;
}
$temp[$pos] = log($scale) * $value;
}
return $temp;
}
// set to the user defined error handler
$old_error_handler = set_error_handler("myErrorHandler");
// trigger some errors, first define a mixed array with a non-numeric item
echo "vector a\n";
$a = array(2, 3, "foo", 5.5, 43.3, 21.11);
print_r($a);
// now generate second array
echo "----\nvector b - a notice (b = log(PI) * a)\n";
/* Value at position $pos is not a number, using 0 (zero) */
$b = scale_by_log($a, M_PI);
print_r($b);
// this is trouble, we pass a string instead of an array
echo "----\nvector c - a warning\n";
/* Incorrect input vector, array of values expected */
$c = scale_by_log("not array", 2.3);
var_dump($c); // NULL
// this is a critical error, log of zero or negative number is undefined
echo "----\nvector d - fatal error\n";
/* log(x) for x <= 0 is undefined, you used: scale = $scale" */
$d = scale_by_log($a, -2.5);
var_dump($d); // Never reached
?>

How to check if included file from a loop comes with errors

I have this kind of loop. On each iteration it should include a file, Included files can come with errors. Once some of included files gets an error the whole process of getting lost. How to prevent breaking of the process?
I tried this try catch but it errors from included files still cause stopping execution of file.
Thanks
foreach ($li_arrays as $index => $li_array) {
if($index == 0){
try {
require 'update.php';
} catch(Exception $e) {
echo "Exception caught with message: " . $e->getMessage() . "\n";
}
}
elseif ($index == 1){
try {
require 'update1.php';
} catch(Exception $e) {
echo "Exception caught with message: " . $e->getMessage() . "\n";
}
}else{
try {
require 'update2.php';
} catch(Exception $e) {
echo "Exception caught with message: " . $e->getMessage() . "\n";
}
}
Errors in php are not recoverable, so they will always lead to the termination of your script.
I am not even sure that you are even talking about Errors, though if you are, this is the answer to your question.
Another thing to be aware of:
Require will throw an E_COMPILE_ERROR if the required file doesn't exist, which is also something you won't be able to catch.
If you don't want to terminate if the script isn't found, use include instead.
At the end I changed "require" with "include" and used this try-catch approach inside each included file.
$attempts = 0;
do {
try
{
////PHP code ///
} catch (Exception $e) {
echo "\n\n======EXCEPTION======\n\n";
var_dump($e);
$attempts++;
sleep(30);
continue;
}
break;
} while($attempts < 5);

PHP Notice with page URL

I have received many PHP Notice in log, but i want to know what page URL where happened Notice, how can i log this info?
[29-Nov-2012 13:58:29] PHP Notice: Array to string conversion in /usr/home/sdf/data/www/sdfsdf.com/core/test.php on line 156
I want to log any info, when get NOTICE, how to log it?
Try to correlate the timestamps in the php error log with your web server access log.
Or, you could tail -f the log and trigger random pages.
Or, set a custom error handler to log all sorts of data.
http://php.net/manual/en/function.set-error-handler.php
function myErrorHandler( $errno, $errstr, $errfile, $errline ){
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
set_error_handler( "myErrorHandler" );
What you could do is to compare you php notice times to your webservers access log in order to find out the URL which caused that notice.
The notice actually does not look like URL dependent.
It indicates, that you're trying to cast a "type" array to a string which happens when you do something like this:
$array = array( 'foo', 'bar' );
echo $array;
$string = 'test' . $array;
printf ( 'foo %s', $array );
To find this error in particular, you could do this to figure out what's wrong (including printing a backtrace using debug_backtrace):
if (is_string($var)) {
//Then it's OK - do whatever you were doing on line 156
} else {
//Something's wrong! Let's log it to some file!
ob_start();
var_dump($var);
print_r(debug_backtrace());
echo "\n----------------------------------------------\n";
$debugContent = ob_get_clean();
$logHandle = fopen("wrong_var_type.log", "a");
if ($logHandle !== false) {
fwrite($logHandle, $debugContent . "\n");
}
#fclose($logHandle);
}
Alternatively, use a logger:
https://stackoverflow.com/a/10800296/247893
You need to set up something to capture error/debug info as it happens, but not show it to the entire world. Not sure if you have session management built in to this application, but you may want to add some basic controls for this exercise.
Sometimes errors are harder to track down if you use functions that are included on multiple pages. The error may appear to happen on the parent page, but is actually triggered in the function, which is included on another page. The error line numbers can be misleading in this case.
If you have intermittent errors that you aren't able to immediately isolate, it may help to get some feedback on what's happening in your script(s). Here's a rough example of how to do some basic debugging in functions:
function get_func_argNames($funcName)
{
$f = new ReflectionFunction($funcName);
$result = array();
foreach ($f->getParameters() as $param)
{
$result[] = $param->name;
}
return $result;
}
function myCoolFunction($arg1, $arg2, $arg3)
{
$debug = false;
$php_function_args = implode(', ',get_func_argNames(__FUNCTION__));
$_debug_txt = "<b><span style='color:blue;'>function</span> <span style='color:darkblue;'>" .__FUNCTION__. "</span></b>($php_function_args)";
if ($debug)
{
EmailAppDev($_debug_txt);
}
// myCoolFunction
$x = $arg1 + $arg2 + $arg3;
return $x
}
Ideally you'll have a session account that can control who $debug is enabled for.
If you aren't using functions, you'll need to set up something similar in strategic areas of your scripts, to find out when and where things are going sour.
Without having your entire app to look at it's pretty hard to give specifics.

Fix PHP Warnings / Errors

Sometimes my scripts produce an error, mostly a Warning.
I have sometimes an idea why this happens, but I have also no clue sometimes why it happens.
Now my question: Is it possible that if a warning gets showed, I can see for what was in the variable?
"Warning: Invalid argument supplied for foreach() in ....";
I get this message but no clue, what was in the variable.
The problem is, it's a script running few hours, with different data, so it's hard to reproduce it. Because, I don't know what was in the variable.
I need this for all kind of Warnings / Errors / Notice / Fatal Error etc.
Thanks for the help.
P.S.
you have a full chapter in php dedicated to errors: http://www.php.net/manual/en/book.errorfunc.php
From the php manual: http://www.php.net/manual/en/function.set-error-handler.php
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
There are a lot of warnings/errors, usually you should just apply logic.
Warning: Invalid argument supplied for foreach() in ....
This means that some variable in the foreach is invalid:
foreach ($array as $value)
or
foreach ($array as $key => $value)
If your script does not match that exact syntax, or if $array is not an actual array, an error would be triggered.
It's not easy to design a debbuging workaround for long-time usage with logging. Basically in this case you would need to adapt the specific foreach with:
foreach (must_be_array($var) as $whatever) ...
Then define that assertion function:
function must_be_array($var) {
if (is_array($var)) return $var;
print_r(array_slice(debug_backtrace(), 1));
// return array(); // to remove now redundant warning
}
The debug_backtrace gives additional context information (called functions and parameters might be helpful). But it won't tell you what should have been in the array variable. It's still going to be an empty variable.
Take a look at set_error_handler() and debug_backtrace().
Also, if there is a specific line/foreach loop that regularly produces this error, add some code something like this on the line before it...
if (!is_array($shouldBeAnArry)) {
// log something here, including data about the variable
// from var_dump(), debug_backtrace() etc etc
} else {
// do the loop
}
This is true of all errors/warnings etc - if an error pops up, add some validation to counter the cause of the error on the line(s) before.

PHP File Size Limit Message

We are using post_max_size and upload_max_filesize PHP variables to prevent users from uploading big files into our web app.
The thing is that the error message that PHP throws is ugly for the users and does not say much (especially for spanish users). So the users report this as a bug or think it is not working.
How can I change this page and show our own page (something more user friendly and in Spanish)?
Thanks a lot!
I think you're looking for set_error_handler().
So something along the lines of (from the manual):
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case UPLOAD_ERR_INI_SIZE:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
$old_error_handler = set_error_handler("myErrorHandler");
catch the error and output a custom message.
try {
// your code
} catch (Exception $e) {
if($e->getCode() == some code){
$message = 'some message';
}else{
$message = 'some other message';
}
}
echo '<div>'.$message.'</div>'

Categories